Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you list the contents of a namespace or module in F#

Tags:

f#

Similar to this question about clojure, is it possible to list the contents of a namespace or module in F#? When I open a namespace for a loaded DLL I'm not receiving an error that the namespace doesn't exist, but then when I try to use the documented functions I'm seeing errors that the namespace doesn't exist.

I'm looking for a programatic way of listing values and methods and not relying on the IDE. For example if I loaded up the F# repl I'm looking for something similar to:

> #r "mylib.DLL"
> open MyLib.Math
> list-namespace-content MyLib.Math;;
   val it : string = """
      MyLib.Math.Add : int -> int -> int
      MyLib.Math.TryDivide : int -> int -> int option
      MyLib.Math.Pi : float
   """
like image 800
jecxjo Avatar asked Jan 08 '23 04:01

jecxjo


1 Answers

As far as I know, there's no such function that does exactly that (I had a glance at the FSharpReflectionExtensions module), but you can write one yourself. All the building blocks are there.

As strange as it may seem, namespaces aren't part of the .NET platform that both F#, C#, and Visual Basic .NET use. At the IL level, types are simply identified by name, culture, assembly, etc. Namespaces simply appear as the first part of the string that makes up a type's name.

However, given an assembly, you can list all its types, or all its public types. Here's an example of the latter, given an F# assembly I recently wrote doing the Tennis kata:

> open System.Reflection;;
> let a = Assembly.LoadFrom @"<path>\Ploeh.Katas.Tennis.PropertyBased.dll";;

val a : Assembly =
  Ploeh.Katas.Tennis.PropertyBased, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null

> let ts = a.GetExportedTypes();;

val ts : System.Type [] =
  [|Ploeh.Katas.PropertyBased.TennisProperties;
    Ploeh.Katas.PropertyBased.Tennis; Ploeh.Katas.PropertyBased.Tennis+Player;
    Ploeh.Katas.PropertyBased.Tennis+Player+Tags;
    Ploeh.Katas.PropertyBased.Tennis+Point;
    Ploeh.Katas.PropertyBased.Tennis+Point+Tags;
    Ploeh.Katas.PropertyBased.Tennis+PointsData;
    Ploeh.Katas.PropertyBased.Tennis+FortyData;
    Ploeh.Katas.PropertyBased.Tennis+Score;
    Ploeh.Katas.PropertyBased.Tennis+Score+Tags;
    Ploeh.Katas.PropertyBased.Tennis+Score+Points;
    Ploeh.Katas.PropertyBased.Tennis+Score+Forty;
    Ploeh.Katas.PropertyBased.Tennis+Score+Advantage;
    Ploeh.Katas.PropertyBased.Tennis+Score+Game|]

By looking at the strings to the left of the last ., you can find the namespace(s) in use - in this case Ploeh.Katas.PropertyBased.

However, you should be aware that namespaces can span multiple assemblies. As an example, System.Collections.Generic.List<'T> is defined in mscorlib, while System.Collections.Generic.Stack<'T> is defined in System. Thus, using Reflection as above will only give you the members defined in a namespace in that particular assembly.

As far as I know, F# modules are compiled to static classes with the [<CompilationMapping(SourceConstructFlags.Module)>] attribute. This means that you can list the modules like this:

> open Microsoft.FSharp.Core;;
> let modules =
    ts
    |> Array.filter
        (fun t -> t.GetCustomAttributes<CompilationMappingAttribute>()
                    |> Seq.exists (fun attr -> attr.SourceConstructFlags = SourceConstructFlags.Module));;

val modules : System.Type [] =
  [|Ploeh.Katas.PropertyBased.TennisProperties;
    Ploeh.Katas.PropertyBased.Tennis|]

If you want to list all the functions in the Tennis module, you can do that like this:

> let tm = modules |> Array.find (fun t -> t.Name = "Tennis");;

val tm : System.Type = Ploeh.Katas.PropertyBased.Tennis

> let functions = tm.GetMethods ();;

val functions : MethodInfo [] =
  [|Player other(Player);
    Microsoft.FSharp.Core.FSharpOption`1[Ploeh.Katas.PropertyBased.Tennis+Point] incrementPoint(Point);
    Point pointFor(Player, PointsData);
    PointsData pointTo(Player, Point, PointsData);
    Score scorePoints(Player, PointsData); Score scoreForty(Player, FortyData);
    Score scoreDeuce(Player); Score scoreAdvantage(Player, Player);
    Score scoreGame(Player); Score score(Score, Player); Score get_newGame();
    Score scoreSeq(System.Collections.Generic.IEnumerable`1[Ploeh.Katas.PropertyBased.Tennis+Player]);
    System.String pointToString(Point);
    System.String scoreToString(System.String, System.String, Score);
    System.String ToString(); Boolean Equals(System.Object);
    Int32 GetHashCode(); System.Type GetType()|]

You may want to filter out some of the inherited methods, like ToString and GetHashCode.

like image 151
Mark Seemann Avatar answered Feb 02 '23 19:02

Mark Seemann