8

I am experimenting with various ways of creating singletons in F#, so that I understand the subtleties better. I don't know if the singleton pattern is ever useful in F#, but I wanted to experiment. And I was surprised by one result involving static constructors on those singleton instances. First I'll show you my code, and then I'll go into more details about my question.

In one project called TrySingleton, I created three modules. Here's Eager.fs:

module TrySingleton.Eager

type EagerClass() =
    do
        printfn "Initializing eager class..."

    static do
        printfn "Static constructor of eager class"

let Instance = EagerClass()

Here's Lazy.fs:

module TrySingleton.Lazy

type LazyClass() =
    do
        printfn "Initializing lazy class..."

    static do
        printfn "Static constructor of lazy class"

let Instance = lazy LazyClass()

And here's how I call them, in Main.fs:

module TrySingleton.Main

[<EntryPoint>]
let main argv =
    printfn "Starting main with args %A" argv
    printfn "Accessing eager instance:"
    printfn "%A" Eager.Instance
    printfn "Accessing lazy instance:"
    printfn "%A" Lazy.Instance.Value
    printfn "Accessing eager instance again:"
    printfn "%A" Eager.Instance
    printfn "Accessing lazy instance again:"
    printfn "%A" Lazy.Instance.Value
    printfn "Success; exiting."
    0

I was expecting the static constructor of the Eager class to run immediately on program startup, and wasn't sure when the Lazy class's static constructor would run. However, here's the output I got:

Starting main with args [||]
Accessing eager instance:
Static constructor of eager class
Initializing eager class...
TrySingleton.Eager+EagerClass
Accessing lazy instance:
Static constructor of lazy class
Initializing lazy class...
TrySingleton.Lazy+LazyClass
Accessing eager instance again:
TrySingleton.Eager+EagerClass
Accessing lazy instance again:
TrySingleton.Lazy+LazyClass
Success; exiting.

It seems that the Eager class is not as eager as I thought it would be. Its static constructor was only run the first time I tried to access the instance, whereas I thought that the static class constructors would run at program startup time.

I guess I don't have much of a question left, except to ask: is this documented anywhere? What documentation did I miss that talks about when static constructors of a class will be run?

rmunn
  • 34,942
  • 10
  • 74
  • 105

1 Answers1

11

I managed to find the answer in the official F# documentation, which I rarely look at anymore because http://fsharpforfunandprofit.com is such a great resource. But the Constructors article in the official F# documentation says (emphasis mine):

In addition to specifying code for creating objects, static let and do bindings can be authored in class types that execute before the type is first used to perform initialization at the type level.

There are followup links to the let Bindings in Classes and do Bindings in Classes articles, which say (emphasis mine, again):

Static let bindings are part of the static initializer for the class, which is guaranteed to execute before the type is first used.

and

A do binding in a class definition performs actions when the object is constructed or, for a static do binding, when the type is first used.

So it looks like I can answer my own question: the answer is that my initial expectations were wrong. The static constructor will not necessarily be run at program startup time, but only when that class is first used. Which means that if you're using the Singleton pattern in two different classes, one of which depends on the other, their constructors (and static constructors) will be run in the order that makes sense given the dependency. (Of course, there might be better, more functional ways to design your code in that scenario; I use it as an illustration rather than as an endorsement of that design.)

rmunn
  • 34,942
  • 10
  • 74
  • 105
  • 1
    Which means that there wasn't really much point to my `Lazy` class: the `Eager` class is only instantiated when first used, so the extra hoop of using `Instance.Value` doesn't seem to gain anything. Therefore, better go the simple route. – rmunn Sep 07 '16 at 04:57
  • Interesting post, so if I understand correctly the difference between the first and third quote lies in [initialization](http://stackoverflow.com/a/39350221/4838058) vs performing actions (side effects e.g. printfn). Addressing the difference in timing: **before the type is first used** vs **when the type is first used**. – Funk Sep 08 '16 at 11:19
  • 3
    @rmunn Note that it's actually not "when the class is first used" but rather "sometime before the class is used". It's up to the runtime when to call that initialization phase. The only guarantee is that it will happen prior to using the type. – Reed Copsey Sep 08 '16 at 19:40