Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# Tuple Constant never initializes

I've declared a tuple like this:

module MyModule =
  let private INVALID_TUPLE = ("0", DateTime.MinValue)

When I reference it lower in the module, it's always null:

let private invalidForNone someOtherTuple =
  match someOtherTuple with
  | None -> INVALID_TUPLE  // it's null
  | Some(t) -> t

Further, when I place a breakpoint on the tuple declaration, it never hits.

If I do the exact same thing in a script (fsx) file, start debugging, execute, the breakpoint on the tuple declaration hits and the reference to the tuple is good.

ILSpy for my module shows that there is some startup code generated that has a Main method that creates INVALID_TUPLE. Apparently, that's not running for some reason?

Here is a sample that reproduces the behavior (now that I realize it has something to do with the way MSTest executes the code). Call this from a C# unit test; result will be null. In fact, no breakpoint in the F# code will execute at all.

module NullTupleTest
open System

let private INVALID_TUPLE = ("invalid", DateTime.MinValue)

let private TupleTest someTuple =
  match someTuple with
  | None -> INVALID_TUPLE
  | Some(dt) -> dt

let Main = TupleTest None
like image 918
dudeNumber4 Avatar asked Dec 28 '16 17:12

dudeNumber4


1 Answers

The error can happen when you run code compiled as an executable in a way that does not run the Main method of the compiled executable - for example by referencing it from a library or by using unit test runner. The solution is to compile the F# project as a library and perhaps have another executable as the entry point. (Alternatively, you could also modify the code to avoid let bound global values, but I'd prefer the first approach.)

This is caused by the fact that the F# compiler handles initialization differently for code compiled as an executable and for code compiled as a library.

  • For libraries, the initialization code is put in a static constructor, which is executed when the field is accessed the first time
  • For executables, the initialization code is put in the Main method and it runs when the application starts (but only when it starts as a normal executable).

I think the reason for this is that the F# compiler tries to keep the order in which initialization happens (from top to bottom). For executables, this can be done by running initializers in the Main method. For libraries, there is no reliable way of doing that (because libraries have no "initialization") and so using static constructors is the next best opion.

like image 139
Tomas Petricek Avatar answered Oct 08 '22 01:10

Tomas Petricek