-
Notifications
You must be signed in to change notification settings - Fork 157
Defining types
Clojure has been adding a variety of new methods for defining types and interfaces, either directly or indirectly. Here’s a list:
proxy
gen-class
-
gen-delegate
(CLR only -- this may be renamed) gen-interface
definterface
reify
deftype
defrecord
defprotocol
ClojureCLR implements all of these with extensions to support ByRef parameters, explicit interface implementation and other facets of the CLR object model not present in the JVM model.
Certain of the macros above — proxy
, reify
, deftype
and defrecord
— implement methods (as opposed to something like definterface
that declares methods defined elsewhere). They need to indicate the signature of the method being implemented.
The CLR has signature elements not present in the JVM. We extend the syntax of signatures for these macros.
Signatures of by-ref methods are handled as in host expressions. (See also ByRef and params parameters.) For example,
(reify P1
(m1 [x #^int y] ...) ; normal
(m2 [x (by-ref #^int y)] ...) ; taking a by-ref parameter
)
At present, we do not have a way to specify properties. If you must implement an interface with a property, you must implement the getter and setter methods directly. This means that you will not be able to access the property directly by name on an instance of the type.
Explicit implementation is required when you want to give different implementations to methods from different interfaces that have the same signature.
(definterface I1
(#^String m1 [#^int x] ))
(definterface I2
(#^int m1 [#^int x]))
Then the following does not work:
(reify
I1
(m1 [x] ...)
I2
(m1 [x] ...))
One of the two needs to be an explicit implementation:
(reify
I1
(m1 [x] ...)
I2
(I2.m1 [x] ...))
Note that the name will have to be fully-qualified.
Here’s a make-me-one-with-everything example:
(definterface I1
(^Int32 m1 [^Int32 x ^String y])
(^Int32 m1 [^String x ^Int32 y])
(^Int32 m2 [^Int32 x ^String y]))
(definterface I2
(^Int32 m1 [#^Int32 x ^String y])
(^String m2 [#^Int32 x ^String y])
(m3 [x y]))
(deftype T2 [a b]
I1
(^Int32 m1 [_ ^Int32 x ^String y] (unchecked-add x (.Length y)))
(^Int32 m1 [_ ^String x ^Int32 y] (unchecked-multiply (.Length x) y))
(^Int32 m2 [this ^Int32 x ^String y] (unchecked-multiply (int 2) (.m1 this x y)))
I2
(m3 [_ x y] (list a b x y))
(^String user.I2.m2 [this ^Int32 x ^String y] (str y " " x)))
(def x (T2. 10 12))
(.m2 x 10 "abc" ) ;=> 26 (picks up the implicit m2)
(.m2 ^user.I2 x 10 "abc") ;=> 26 (still picks up the implicit m2, need more typing
(.m2 ^user.I2 x (int 10) "abc") ;=> "abc 10" -- picks up user.I2.m2
When using reify
and company on the JVM, it is not necessary to implement all the methods in implemented interfaces. The ClojureJVM code does not insert them, and the JVM does not care. Only if you try to invoke an unimplemented method will you discover this, via an exception being thrown.
The CLR cares. If you define a class and do not implement all the methods, either the class must be marked as abstract or you will get an invalid type exception thrown. Abstract classes are useless here, so ClojureCLR must provide dummy implementations of all the methods in all the interfaces being implemented. These dummies throw a NotImplementedException
when called.
When generating these dummy methods, we go through the list of all interface methods, subtract the ones the user provided implementations for, and dummy up the rest. Where two interfaces define identical methods (name + arg types + return types), only a single implementation will be provided.
This works except for the case where two interfaces provide methods with the same name and argument types but different return types. One can be implemented directly. One must be implemented via explicit implementation. The problem: which one? We have no way to know, and which we choose determines which interface we must cast to to pick up the desired implementation. Therefore, it is an error if the user does not provide implementations for at least one of the methods in this case.
Certain of the macros above — gen-interface
, gen-class
, definterface
and defprotocol
— define new methods (as opposed to identifying methods in existing interfaces or classes). At present, definterface
and defprotocol
are defined in terms of gen-interface, so a compatible solutions are desired.
ByRef parameters in gen-interface
, definterface
and defprotocol
are specified using the same by-ref
syntactic form used in host expressions.
For example,
(gen-interface :name I1
:extends [I2 I3]
:methods [
[m1 [Object Int32] String] ; normal
[m2 [Object (by-ref Int32)] String] ; taking a by-ref parameter
])
(definterface I1
(^String m1 [x ^int y] ) ; normal
(^String m2 [x (by-ref ^int y)] ) ; taking a by-ref parameter
)
At present, we do not support properties directly, but properties generate methods of the property name, prefixed by get_
or set_
respectively.