2
votes

If I have a module of name MyModule defined in an .fsx script and referenced from within another .fsx script; is it possible to determine at runtime if the module defines a variable foo?

I am trying to implement something like this:

let fooWithDefault = 
    let cfgType:Type = typedefof<MyModule>
    let propOpt = 
        cfgType.GetProperties()
        |> Seq.tryFind( fun p -> p.Name = "foo")

    match propOpt with
    | Some foo -> foo.GetValue(null).ToString()
    | None -> "My Default Value for f"

The above attempt fails with the error:

The type 'MyModule' is not defined

1
I think there will be two problems with this, MyModule should be a class, not a module (which is probably not a type), and in an fsx file FSI will prepend an artifical namespace if you use #load. If you use a class in a dll or an .fs file, it might work. - s952163
I'm pretty sure a module would compile to a type: The docs here: docs.microsoft.com/en-us/dotnet/articles/fsharp/… suggest "It is implemented as a common language runtime (CLR) class that has only static members." Don't know about how FSI deals with it though. Any way to know or determine which artificial namespace it prepends? - Stewart_R
It certainly does compile to a type but actually getting a module's type requires some shenanigans. This answer shows the usual way of doing that if you really need to: stackoverflow.com/a/14706890/5438433 - TheInnerLight
Thanks folks - Having given it a bit more thought, I can work around it with Assembly.GetExecutingAssembly().GetTypes() |> Seq.find (fun t -> t.Name = "MyModule") - risk of a name collision is negligible and there are no perf worries in my use case - Stewart_R
you're correct that a module is like a static class for CLR purposes. If you run/load the code that creates the type you'll see somethinge like: namespace FSI_0005 prepended. - s952163

1 Answers

1
votes

One possible (somewhat kludgy) approach is to enumerate all the types in the executing assembly:

let fooWithDefault = 
    let cfgType = 
        Assembly.GetExecutingAssembly().GetTypes()
        |> Seq.find(fun t -> t.Name = "MyModule")

    let propOpt = 
        cfgType.GetProperties()
        |> Seq.tryFind( fun p -> p.Name = "foo")

    match propOpt with
    | Some foo -> foo.GetValue(null).ToString()
    | None -> "My Default Value for f"

If using this approach we'd need to be aware that there is a risk of a name collision if we have 2 entities named 'MyModule'. Also, enumerating all the types in the executing assembly is surely sub-optimal.

With that said it seems to work ok for the limited test-cases I have attempted