Lately I’ve been learning F#. I have used SML before for little experiments. SML is one of F#’s parents, and a lot of the basic syntax is familiar. The things I’ve been learning and discovering the most about are the large-scale issues. How does C# call F#? F# call C#? Or even, F# call F#? I wasn’t expecting that last one to be a challenge, but it was. Here’s why…
I started in the way I start almost any project: code in one place, tests in another. My first goal is to get the tests calling the code. This means a trivial test like making sure that a function returns true, when it… returns true.
This is about the simplest F# solution I can picture. I’m already stumbling though, as evidenced by the red squiggly underneath always_true in TestsFile.fs. That squiggle is for a “The value or constructor ‘always_true’ is not defined.” error. But it is defined, over in CodeFile.fs, and I have no idea how to be more specific because all my declarations are top-level. I haven’t had to add any namespace or type names, which is nice. But as a result, I don’t know which namespaces or types these items are part of.
Opening the assemblies in reflector might help:
There it is! My top-level declarations in CodeFile.fs are part of the CodeFile type in no namespace. always_true is there as a public static method, and my_true is there as a get-only public static property. There are three other internal types in there that have been created by the compiler.
F# will provide this automatic module even if an explicit module is declared in the file. For example, you might think that since always_true and my_true ended up as part of the CodeFile type, that the code in the screenshot above is equivalent to
module CodeFile = let my_true = true let always_true () = my_true
Just to check, I’ll compile that and open it up in Reflector again.
Instead of overwriting the automatic module, the explicitly declared module is nested inside of the automatic module. The public static type CodeFile, has a nested public static type CodeFile+CodeFile. Not what I was expecting.
The automatic module will continue to be produced unless I add a namespace declaration.
namespace Code module CodeFile = let my_true = true let always_true () = my_true
This ends up with a structure more familiar from C# work. CodeFile is a type within the Code namespace.
I now have a pretty good picture of how the automatic naming works, but there are still scenarios I’d like to try. First, what happens if I declare a namespace but don’t include an explicit module declaration? I have a hunch, but I’ll try it to confirm.
This surprised me! This is a compile-time error. There is no automatic module creation to make this work.
namespace Code let my_true = true // compile-time error: "Namespaces may not contain values. // Consider using a module to hold your value declarations." module CodeFile = let always_true () = my_true // ok
The second question I had in mind was about mixing a module declaration and top-level declarations in one file. This has been resolved by the previous findings, though. If my file has a namespace declaration, the top-level declarations will be illegal. If it does not have a namespace declaration, then an automatic module with the name of the file will be created.
This is a simpler result than I was expecting, because it means there is only a single rule at play:
- If an .fs file does not include a namespace declaration, a module will be created in the top-most namespace to contain the file’s declarations.
Knowing this, I can return my code under test to how it looked before and get my tests working.
In CodeFile.fs, in the Code project:
#light let always_true () = true
In TestsFile.fs, in the Tests project:
#light open Xunit [
] let true_is_true () = Assert.True(CodeFile.always_true ())