Given the following:
let f<'a,'b> = typeof<'a>.Name, typeof<'b>.Name //val f<'a,'b> : string * string
let g<'a,'b>() = typeof<'a>.Name, typeof<'b>.Name //val g<'a,'b> : unit -> string * string
the following quotations produce what appear to be identical Expr
s:
let fq = <@ f<int,string> @> //Call (None, System.Tuple`2[System.String,System.String] f[Int32,String](), [])
let gq = <@ g<int,string>() @> //Call (None, System.Tuple`2[System.String,System.String] g[Int32,String](), [])
Digging around with the debugger, I can't see any way to tell that f
is a generic value whereas g
is a generic function: is it possible to tell this just from fq
and gq
? Since F# can tell the difference between f
and g
across assemblies, I figure there's a good chance I may be able to get at the meta data from the quotation. Though an issue may be that F# appears to in fact compile a version of f
which is a function unit -> string * string
(looking at disassembled code; presumably for interop with other .NET languages), so if the quotation is using the function version, the information may be lost.
Update
From @Tomas's guidance, here's what I've come up with:
let isGenericValue (mi:MemberInfo) =
try
let mOrV =
FSharpEntity.FromType(mi.DeclaringType).MembersOrValues
|> Seq.find (fun mOrV -> mOrV.CompiledName = mi.Name)
not mOrV.Type.IsFunction
with
| :? System.NotSupportedException -> true //for dynamic assemblies, just assume idiomatic generic value
This can be used matching on Patterns.Call(_,mi,_)
, where the second argument mi
is a MemberInfo
instance. However, there is one issue: it doesn't work for dynamic assemblies (like FSI).
Under the cover, generic values are compiled as generic methods, because the .NET runtime doesn't have any notion of "generic fields" (or something like that). I think that F# distinguishes between the two using the information stored in the binary blob "FSharpSignatureData" in resources.
The way to work with this binary information is to use F# Metadata Reader from F# PowerPack. If you compile the two lines you write into test.exe
, then you write this:
let met = Microsoft.FSharp.Metadata.FSharpAssembly.FromFile(@"C:\temp\test.exe")
for e in met.Entities do
// Prints 'Test' for the top-level module
printfn "%A" e.DisplayName
for e in e.MembersOrValues do
// Prints:
// "f" false (for value 'f')
// "g" true (for function 'g')
printfn "- %A %A" e.DisplayName e.Type.IsFunction
If you wanted to distinguish between the two using reflection, then you'd have to find some way to connect the metadata information with the reflection MethodInfo
(which probably could be done via CompiledName
property).