7

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
Guy Coder
  • 24,501
  • 8
  • 71
  • 136
dudeNumber4
  • 4,217
  • 7
  • 40
  • 57
  • How do you know that it's null? Is something actually not working somewhere? – Fyodor Soikin Dec 28 '16 at 18:11
  • Well yes, I get an NRE downstream, and I also know it's null because the debugger tells me so. – dudeNumber4 Dec 28 '16 at 18:24
  • Debugger is unreliable. Don't rely on it. Can you show where you get the NRE and how the supposedly null value gets there? Or provide another minimal reproducible example? Also, see "[How to ask a good question](http://stackoverflow.com/help/how-to-ask)". – Fyodor Soikin Dec 28 '16 at 18:35
  • I can't repro. I tried `let test = invalidForNone None` and `MyModule.test` outside. It returns the tuple. – Asti Dec 28 '16 at 18:55
  • 1
    I think I have seen this behaviour before - when running code from a test runner (rather than in a "normal" way). It was somehow not executing the static initializer of the module, but I'm not entirely sure how to reproduce or avoid that. – Tomas Petricek Dec 28 '16 at 19:38
  • @TomasPetricek Yes, running from MSTest unit tests. Same code in console app initializes fine (breakpoint on tuple hits). – dudeNumber4 Dec 28 '16 at 19:44
  • 1
    @FyodorSoikin Sample added (didn't know it was MSTest related). Debugger is unreliable? Thus, F# is not production usable? Obviously, that's what any team on earth would say if that's truly the case. – dudeNumber4 Dec 28 '16 at 19:56
  • @dudeNumber4 Are you compiling the test project as a library or as an executable? (I think this might work with libraries, but perhaps not with executables.) – Tomas Petricek Dec 28 '16 at 19:58
  • @TomasPetricek C# test project is library (MSTest default). – dudeNumber4 Dec 28 '16 at 20:00
  • As for F# being production ready - there is a large number of companies using it for production (http://fsharp.org/testimonials) and perhaps the most successful Microsoft-tech based startup uses it heavily (https://tech.jet.com/blog/2015/03-22-on-how-jet-chose). F# itself works great, though it does not get always get perfect treatment in all MS products (like in MSTest). – Tomas Petricek Dec 28 '16 at 20:01
  • @dudeNumber4 The question was about the F# project - is that a library or an executable? – Tomas Petricek Dec 28 '16 at 20:01
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/131742/discussion-between-dudenumber4-and-tomas-petricek). – dudeNumber4 Dec 28 '16 at 20:02
  • 1
    @dudeNumber4 F# is production ready. Visual Studio debugger is not. – Fyodor Soikin Dec 28 '16 at 21:04
  • @FyodorSoikin I guess I'm confused. What other IDE would one use? – dudeNumber4 Dec 29 '16 at 02:24
  • There is no better IDE than Visual Studio at the moment (in my opinion). There seems to be a misunderstanding here: the claim that a thing is the best thing in existence does not imply that it's perfect. It's sort of like with democracy :-) – Fyodor Soikin Dec 29 '16 at 02:56

1 Answers1

7

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.

Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553