- Notifications
You must be signed in to change notification settings - Fork160
Calling generic methods
We're talking here about generic methods, not methods in generic classes.
The use of generic methods in .Net libraries has increased significantly in recent years. Linq, for example, relies on generic methods as extension methods to other types. Linq makes significant use of dynamic call sites and type inferencing on generic method type parameters for its magic.
ClojureCLR shares some of that magic.
(import 'System.Linq.Enumerable)(def r1 (Enumerable/Where [1 2 3 4 5] even?))(seq r1) ; => (2 4)
The generic methodWhere
is overloaded with the following signatures.
public static IEnumerable<TSource> Where<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate)public static IEnumerable<TSource> Where<TSource>( this IEnumerable<TSource> source, Func<TSource, int, bool> predicate)
When analyzing(Enumerable/Where [1 2 3 4 5] even?)
, the[1 2 3 4 5]
is aclojure.lang.PersistentVector
, which supportsIEnumerable<Object>
and hence can match against theIEnumerable<TSource>
in the first argument position. The value ofeven?
is aclojure.lang.IFn
, specifically aclojure.lang.AFn
. In ClojureCLR,clojure.lang.AFn
implements interfaces related to reporting the number of arguments supported in calls and to participating in generic type parameter inferencing. The methodeven?
reports that it supports one argument and does not support two arguments. Therefore, it supports casting toFunc<TSource, bool>
but not toFunc<TSource, int, bool>
, allowing discrimination between the two overloads. (Func<TSource, bool>
is a delegate for a method taking one argument of typeTSource
and returning a value of type 'bool'.)
If we had a functionf
that supports one and two arguments, the type inferencing illustrated in the previous example would not work.
user=> (Enumerable/Where [1 2 3 4 5] f)ArgumentTypeException Multiple targets could match: Where(IEnumerable`1, Func`2), Where(IEnumerable`1, Func`3) .CallSite.Target (:0)
There are two ways around this. One way is to write a simple anonymous function taking one argument that calls f:
(Enumerable/Where [1 2 3 4 5] #(f %1))
The second way is to explicitly declare types. Macrossys-func
andsys-action
are available to create from anIFn
delegates of typeSystem.Func<,...>
andSystem.Action<,...>
:
(Enumerable/Where [1 2 3 4 5] (sys-func [Object Boolean] [x] (f x))))
The first is preferable. However, when type inferencing does not sufficesys-func
andsys-action
can allow you to do the inference for the system.
(def r2 (Enumerable/Range 1 10))(seq r2) ;=> (1 2 3 4 5 6 7 8 9 10)(Enumerable/Where r2 even?) ;=> (2 4 6 8 10)
This used to fail due to problems with Range iterator conversions, but it works now. However, I'll still use it as an example of how to usesys-func
:
(Enumerable/Where r2 (sys-func [Int32 Boolean] [x] (even? x)))
BTW, the following might work if r2 did not support IEnumerable in some flavor but did support conversion to anISeq
:
(Enumerable/Where (seq r2) (even? x))
This works because the type implementing the sequence onr2
does supportIEnumerable<Object>
.
There are some cases where you need to explicitly supply type arguments to a generic method. For example, the following fails:
(def r3 (Enumerable/Repeat 2 5) ;=> FAILS
The error message states:
InvalidOperationException Late bound operations cannot be performed on types ormethods for which ContainsGenericParameters is true. System.Reflection.RuntimeMethodInfo.ThrowNoInvokeException (:0)
We can cause the type arguments on theRepeat<T>
method to be filled in using thetype-args
macro:
(def r3 (Enumerable/Repeat (type-args Int32) 2 5)) ; use type-args to supply the type parameters to the generic method(seq r3) ;=> (2 2 2 2 2)