7
[<ReflectedDefinition>]
module Foo = 
    let x = 5
    let y () = 6
    let z a = a

I tried to find out how to get the AST in this situation a couple of times now and keep failing. Time to ask the question here.

So far, I thought that a module would be mappped to a class with static members internally and as such, it should be the equivalent of:

[<ReflectedDefinition>] 
type Foo =
    static member x = 5
    static member y () = 6
    static member z a = a

let bar_members = 
    typeof<Bar>.GetMethods()
    |> Array.filter (fun mi -> match mi with | MethodWithReflectedDefinition x -> true | _ -> false)
    |> Array.map (fun m -> sprintf "%s: %A" (m.Name) (Expr.TryGetReflectedDefinition(m :> MethodBase) ) )

In the latter case, I could use typeof<Foo>.GetMembers() (or GetMethods()?!), cast it to Reflection.MethodBase and use this as an argument for Expr.TryGetReflectedDefinition().

But unfortunately, this is not working with the module version.

So, how to do it?

If you want to play with the code, you might want to open some namespaces:

open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.DerivedPatterns
open Microsoft.FSharp.Reflection
open System.Reflection
BitTickler
  • 10,905
  • 5
  • 32
  • 53

2 Answers2

4

The problem comes go down to actually getting the type of the Module. In order to do that, there's a great answer here by Phillip Trelford: https://stackoverflow.com/a/14706890/5438433

Basically, you add a helper value to your module which returns the type of that module:

[<ReflectedDefinition>]
module Foo = 
    type internal IMarker = interface end
    let fooType = typeof<IMarker>.DeclaringType

    let x = 5
    let y () = 6
    let z a = a

You can then use fooType to retrieve the reflected definitions.

 let foo_members = 
     Foo.fooType.GetMethods()
     |> Array.filter (fun mi -> match mi with | MethodWithReflectedDefinition x -> true | _ -> false)
     |> Array.map (fun m -> sprintf "%s: %A" (m.Name) (Expr.TryGetReflectedDefinition(m :> MethodBase) ) )

I can then, e.g. print the results:

[|"get_fooType: Some PropertyGet (Some (Call (None, TypeOf, [])), DeclaringType, [])"; "get_x: Some Value (5)"; "y: Some Lambda (unitVar0, Value (6))"; "z: Some Lambda (a, a)"|]

Community
  • 1
  • 1
TheInnerLight
  • 12,034
  • 1
  • 29
  • 52
  • I saw this question. And I tried the answer using a ``GetModuleType`` helper function. I tried in interactive and it did not work. I think your answer is roughly the same. Is there a way without powerpack (which should be obsolete by now) and which works in interactive, as well? And also which works without adding tags or alike to the module? I remember I asked in some other forum about it and never got an answer. Why is there not some ``Microsoft.FSharp.Reflection.Module.GetType()`` or alike? – BitTickler Feb 23 '16 at 13:32
  • @BitTickler It's only the first answer to that question that requires powerpack. The example code I posted there works fine in interactive. – TheInnerLight Feb 23 '16 at 13:35
  • Indeed - just replicated and it works! Well - I will use this as a workaround until some Microsoft guys decide there should be a straightforward way to do it! My thanks! – BitTickler Feb 23 '16 at 13:38
  • @BitTickler I had a quick look on the F# language design user voice site and it doesn't seem this has been brought up there before, could be worth listing it there? https://fslang.uservoice.com/ – TheInnerLight Feb 23 '16 at 13:47
  • I stumbled across this while thinking about ways to (meta) program applications which require different languages to cooperate. Example: SecondLife scripting language and http with script for some SecondLife AJAX based HUD. Or: Some f# backend for some websocket html application. More esoteric: Program C in F#, test it in F#, then generate code from it, hopefully with fewer bugs. Anytime I think about such things, I stumble across the problem on how to use [] on a module. Corner case or useful to the public? You decide ;) – BitTickler Feb 23 '16 at 14:13
0

For the use case, when the reflected definitions are in another assembly (like an F# dll, for example), you can do without the marker interface trick, as shown below:

open System
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.DerivedPatterns
open Microsoft.FSharp.Reflection
open System.Reflection
open FSharp.Reflection.FSharpReflectionExtensions

let tryGetReflectedModules (a : Assembly) : seq<TypeInfo>  = 
    a.DefinedTypes 
    |> Seq.filter 
        (fun dt -> 
            dt.CustomAttributes
            |> Seq.map (fun cad -> cad.AttributeType)
            |> Seq.filter ((=) (typeof<ReflectedDefinitionAttribute>))
            |> Seq.isEmpty
            |> not
        )

let astFromReflectedDefinition (mi : MethodInfo) : Expr option =
    mi :> MethodBase |> Expr.TryGetReflectedDefinition

let reflectedMethodsOfAModule (m : System.Type) : (MethodInfo * Expr) [] =
    m.GetMethods()
    |> Array.map (fun m -> (m,astFromReflectedDefinition m))
    |> Array.filter (snd >> Option.isSome)
    |> Array.map (fun (x,y) -> (x, Option.get y))

let reflectAssembly (assemblyPath : string) =
    let a = System.Reflection.Assembly.LoadFile(assemblyPath) 
    a 
    |> tryGetReflectedModules
    |> Seq.map (fun x -> (x,reflectedMethodsOfAModule (x.AsType())))

Where, for example, the assembly I used for testing the code above looked like this:

namespace Input

[<ReflectedDefinition>]
module Api =
    let trace s = 
        for _ in [0..3] do System.Diagnostics.Trace.WriteLine s

[<ReflectedDefinition>]
module Foo =
    let foobar (x : string) : string =
        x.ToUpper()

You get the top level types in the assembly, which just so happen to be the (static) classes, representing the modules of the Fsharp assembly and test for the ReflectedDefinitionAttribute presence. Then, you take it from there.

BitTickler
  • 10,905
  • 5
  • 32
  • 53