2

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

Stewart_R
  • 13,764
  • 11
  • 60
  • 106
  • 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 Sep 05 '16 at 09:33
  • I'm pretty sure a module would compile to a type: The docs here: https://learn.microsoft.com/en-us/dotnet/articles/fsharp/language-reference/modules 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 Sep 05 '16 at 09:41
  • 2
    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: http://stackoverflow.com/a/14706890/5438433 – TheInnerLight Sep 05 '16 at 09:54
  • 1
    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 Sep 05 '16 at 09:57
  • 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 Sep 05 '16 at 09:57

1 Answers1

1

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

Stewart_R
  • 13,764
  • 11
  • 60
  • 106