nasser / magic

Morgan And Grand Iron Clojure
http://nas.sr/magic/
366 stars 18 forks source link

The deftype equality methods must be dealt with reader conditionals #216

Closed skydread1 closed 3 years ago

skydread1 commented 3 years ago

Problem

We consider the following clojure ns:

(ns flybot.my-ns
  (:require  [clojure.test :refer [deftest testing is]]))

(defprotocol DummyProtocol
  "Dummy protocol."
  (-method1 [this]
    "dummy method")
  (-method2 [this n]
    "dummy method with param `n`"))

(deftype MyType [field1 field2]
  ;; Protocol methods
  DummyProtocol
  (-method1 [_] field2)
  (-method2 [_ n] n)

  ;; Override equals method to compare the types.
  Object
  (hashCode [_] (.hashCode field1))
  (equals [_ other]
    (and (instance? MyType other) (= field1 (.field1 other)))))

(deftest type-equality
  (testing "the 2 types are equals."
    (is (= (MyType. 1 2)
           (MyType. 1 2)))))

The expecting results are

Compilation error

However, when I try to compile, I have a magic error:

Exception System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> clojure.lang.ClojureException: No match binding methodhashCode (compiling ::)
  at <magic>clojure_core$load-lib__0.invokeTyped (System.Object prefix, System.Object lib, clojure.lang.ISeq options) [0x005e1] in <5fd64cb849034e8aa1f26183a2c6d237>:0 
  at <magic>clojure_core$load-lib__0.doInvoke (System.Object prefix, System.Object lib, System.Object options) [0x00000] in <5fd64cb849034e8aa1f26183a2c6d237>:0 
  at clojure.lang.RestFn.applyTo (clojure.lang.ISeq arglist) [0x000d2] in <c189306613024f40b3db05e374c76eec>:0 
  at clojure.lang.AFunction+MetaWrapper.doInvoke (System.Object args) [0x00000] in <c189306613024f40b3db05e374c76eec>:0 
  at clojure.lang.RestFn.applyTo (clojure.lang.ISeq arglist) [0x0008c] in <c189306613024f40b3db05e374c76eec>:0 
  at <magic>clojure_core$apply__0.invokeTyped (clojure.lang.IFn f, System.Object x, System.Object args) [0x00017] in <5fd64cb849034e8aa1f26183a2c6d237>:0 
  at <magic>clojure_core$apply__0.invoke (System.Object f, System.Object x, System.Object args) [0x00000] in <5fd64cb849034e8aa1f26183a2c6d237>:0 
  at <magic>clojure_core$load-libs__0.invokeTyped (clojure.lang.ISeq args) [0x00568] in <5fd64cb849034e8aa1f26183a2c6d237>:0 
  at <magic>clojure_core$load-libs__0.doInvoke (System.Object args) [0x00000] in <5fd64cb849034e8aa1f26183a2c6d237>:0 
  at clojure.lang.RestFn.applyTo (clojure.lang.ISeq arglist) [0x0008c] in <c189306613024f40b3db05e374c76eec>:0 
  at <magic>clojure_core$apply__0.invokeTyped (clojure.lang.IFn f, System.Object x, System.Object y, System.Object args) [0x00019] in <5fd64cb849034e8aa1f26183a2c6d237>:0 
  at <magic>clojure_core$apply__0.invoke (System.Object f, System.Object x, System.Object y, System.Object args) [0x00000] in <5fd64cb849034e8aa1f26183a2c6d237>:0 
  at <magic>clojure_core$use__0.invokeTyped (clojure.lang.ISeq args) [0x00019] in <5fd64cb849034e8aa1f26183a2c6d237>:0 
  at <magic>clojure_core$use__0.doInvoke (System.Object args) [0x00000] in <5fd64cb849034e8aa1f26183a2c6d237>:0 
  at clojure.lang.RestFn.invoke (System.Object arg1, System.Object arg2) [0x00041] in <c189306613024f40b3db05e374c76eec>:0 
  at <magic>expr_155.eval () [0x0002f] in <f3a5126bb9aa44019e83899263a294c9>:0 
  at (wrapper managed-to-native) System.Reflection.RuntimeMethodInfo.InternalInvoke(System.Reflection.RuntimeMethodInfo,object,object[],System.Exception&)
  at System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x0006a] in <b18e5763ec3f41e68a8fe16d7f5e0bee>:0 
   --- End of inner exception stack trace ---
  at Magic.Dispatch.InvokeUnwrappingExceptions (System.Reflection.MethodBase method, System.Object target, System.Object[] args) [0x00015] in <79947ecf2ac04d5b8a37881fb4867092>:0 
  at Magic.Dispatch.InvokeInstanceMethod (System.Object o, System.String name, System.Object[] args) [0x0001c] in <79947ecf2ac04d5b8a37881fb4867092>:0 
  at <magic>magic_api$compile-expression__0.invoke (System.Object expr, System.Object ctx, System.Object opts) [0x00437] in <9110e5b810ba402c9cfcd70e01a7ec5b>:0 
  at <magic>magic_api$compile-expression-top-level__0.invoke (System.Object expr, System.Object ctx, System.Object opts) [0x0011b] in <9110e5b810ba402c9cfcd70e01a7ec5b>:0 
  at <magic>magic_api$eval__0.invoke (System.Object expr) [0x00131] in <9110e5b810ba402c9cfcd70e01a7ec5b>:0 
  at clojure.lang.AFn.ApplyToHelper (clojure.lang.IFn fn, clojure.lang.ISeq argList) [0x00082] in <c189306613024f40b3db05e374c76eec>:0 
  at clojure.lang.AFn.applyTo (clojure.lang.ISeq arglist) [0x0000b] in <c189306613024f40b3db05e374c76eec>:0 
  at clojure.lang.AFunction+MetaWrapper.doInvoke (System.Object args) [0x00000] in <c189306613024f40b3db05e374c76eec>:0 
  at clojure.lang.RestFn.invoke (System.Object arg1) [0x00029] in <c189306613024f40b3db05e374c76eec>:0 
  at clojure.lang.Var.invoke (System.Object arg1) [0x00010] in <c189306613024f40b3db05e374c76eec>:0 
  at <magic>clojure_core$eval__0.invoke (System.Object form) [0x0000a] in <5fd64cb849034e8aa1f26183a2c6d237>:0 
  at <magic>nostrand_repl$cli__0.invoke (System.Object p__1618) [0x0020c] in <bfb31fc82b3440abb71b60aaff1e915f>:0 

Workaround

The workaround is to use the reader conditionals to handle the object equality such as:

(deftype MyType [field1 field2]
  ;; Protocol methods
  DummyProtocol
  (-method1 [_] field2)
  (-method2 [_ n] n)

  ;; Override equals method to compare the types.
  #?@(:clj
      [Object
       (hashCode [_] (.hashCode field1))
       (equals [_ other]
               (and (instance? MyType other) (= field1 (.field1 other))))]
      :cljr
      [clojure.lang.IHashEq
       (hasheq [_] (hash field1))
       clojure.lang.IPersistentCollection
       (equiv [_ other]
               (and (instance? MyType other) (= field1 (.field1 other))))]))

Suggestion

There probably is a way to handle this directly in magic so it is transparent for the user.

nasser commented 3 years ago

It is failing to compile because hashCode and equals are Java methods. The equivalent on the CLR are Object.GetHashCode and Object.Equals. This is a persistent problem with anything hosty, such as type definitions. MAGIC already handles these differences internally when creating types, but if the user is overriding superclass methods they need to use the correct name and casing depending on the platform. I do not see an easy way to do this without reader conditionals, but I am open to suggestions.

skydread1 commented 3 years ago

Yes, that's what I though also.

One way to do it would be to have a map nativeJava-nativeC# and referring to it every time the function is not found in CLR. It would need to be updated regularly and it is valid only for the functions that are really equivalent in both java and c# which is not often the case (different args etc).

I guess, keeping the reader conditionals is fine since they are quite easy to read and most of the users won't have much native java function in their code. (Hopefully).

nasser commented 3 years ago

Agreed on all points. Host-specific code is not portable by definition!