This F# code
open System
open FSharp.Reflection
let getInterfaces f: Type seq = Unchecked.defaultof<_>
//retrieve interfaces constraining the env argument of f
let a (env: #IDisposable & #ICloneable) = env.Clone()
let interfaces = getInterfaces a
is meant to obtain
seq { typeof<IDisposable>; typeof<ICloneable> }
as value for interfaces.
However it does not compile with the following errors in the last line
Error (active) FS0331 The implicit instantiation of a generic construct at or near this point could not be resolved because it could resolve to multiple unrelated types, e.g. 'ICloneable' and 'IDisposable'. Consider using type annotations to resolve the ambiguity
Error (active) FS0071 Type constraint mismatch when applying the default type 'IDisposable' for a type inference variable. The type 'IDisposable' is not compatible with the type 'ICloneable' Consider adding further type constraints
The purpose is to implement a function getInterfaces that takes as argument another function like a (having an argument named env constrained to implement a number of interfaces) and returns the collection of those interfaces as System.Type instances
1 Answer 1
The problem is that you can't pass generic functions as parameters. Or as values at all. Once you treat a function as a value, it has to be instantiated at that point.
And that is what the compiler has trouble with. When you pass function a
to getInterfaces
as a parameter, the compiler has to instantiate a
with some generic argument. Since it has no other information, it's trying to find the "safest" option, so to speak. If there was just one constraint, the compiler would use that constraint for instantiation. Say, if you removed ICloneable
from the signature, the compiler would instantiate the function as a<IDisposable>
, and then pass that to getInterfaces
. But with two constraints it doesn't see a type that would be compatible with both.
What you can do is work with a method instead. For example, we could implement getInterfaces
such that it expects to be given a type that has a single method, and returns that method's generic constraints:
type I =
static member a (env: #IDisposable & #ICloneable) = env.Clone()
let getInterfaces<'intf> : Type seq =
let method = typeof<'intf>.GetMethods() |> Array.head
seq {
for a in method.GetGenericArguments() do
for c in a.GetGenericParameterConstraints() do
yield c
}
let interfaces = getInterfaces<I> |> Seq.toList
Alternatively, you could give the method a type and name of a method on that type:
let getInterfaces (typeWithMethod: Type) methodName : Type seq =
let method = typeWithMethod.GetMethod(methodName)
seq {
for a in method.GetGenericArguments() do
for c in a.GetGenericParameterConstraints() do
yield c
}
let interfaces = getInterfaces (typeof<I>) (nameof I.a)
And this will also work for top-level functions in a module, because modules are compiled as .NET classes. Unfortunately there is no straight way to get a Type
object representing the module. You might get the module's type by full name, but that's fragile. Or, if you know a type declared inside that module, you could get the module as parent of that type:
module M =
type Dummy = interface end
let a (env: #IDisposable & #ICloneable) = env.Clone()
let interfaces = getInterfaces (typeof<M.Dummy>.DeclaringType) (nameof M.a)
Sadly, this won't work for a function declared inside another function. Even though it is compiled as a method of some type somewhere, there is no sane or reliable way to find that type.