0

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

asked Mar 7 at 1:32

1 Answer 1

2

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.

answered Mar 7 at 3:56

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.