I've often wondered how the F# compiler compiles the do binding. Here is the skinny on that (Using v4.0.30319.1 of the compiler).

Definition

The docs give the following definition of the do binding:

"A do binding is used to execute code without defining a function or value. Also, do bindings can be used in classes...

Use a do binding when you want to execute code independently of a function or value definition. The expression in a do binding must return unit. Code in a top-level do binding is executed when the module is initialized. The keyword do is optional.

Attributes can be applied to a top-level do binding. For example, if your program uses COM interop, you might want to apply the STAThread attribute to your program. You can do this by using an attribute on a do binding, as shown in the following code."

In a Module

First off lets talk about using the do binding directly in a module. To set the stage, lets say we have 2 modules with exactly the same code except one says hello and one says goodbye. Notice that the Hello module is the last module in the project (Remember, in F# file order matters).

image

Goodbye.fs:

module Goodbye

    open System

    let getTime () = DateTime.Now.ToString()
    let message = "goodbye there: "

    Console.WriteLine(message + getTime ())

    do
        Console.WriteLine("hasta luego")

    do
        Console.WriteLine("oh bai")
        Console.ReadKey() |> ignore

Hello.fs:

module Hello

    open System

    let getTime () = DateTime.Now.ToString()
    let message = "hello there: "

    Console.WriteLine(message + getTime ())

    do
        Console.WriteLine("hola")

    do
        Console.WriteLine("oh hai")
        Console.ReadKey() |> ignore

The previous two files are compiled as follows (Represented in C#, with some of the compiler noise removed).

public static class Goodbye
{
    public static string getTime() { return DateTime.Now.ToString(); }
    public static string message { get { return "goodbye there: "; } }
}

public static class Hello
{
    public static string getTime() { return DateTime.Now.ToString(); }
    public static string message { get { return "hello there: "; } }
}

internal static class $Goodbye
{
    static $Goodbye()
    {
        string message = Goodbye.message;
        Console.WriteLine(Goodbye.message + Goodbye.getTime());
        Console.WriteLine("hasta luego");
        Console.WriteLine("oh bai");
        ConsoleKeyInfo info2 = Console.ReadKey();
    }
}

internal static class $Hello
{
    public static void main@()
    {
        string message = Hello.message;
        Console.WriteLine(Hello.message + Hello.getTime());
        Console.WriteLine("hola");
        Console.WriteLine("oh hai");
        ConsoleKeyInfo info2 = Console.ReadKey();
    }
}

A couple of things to note here. First of all, all the code (Whether defined in a do binding or directly on the module) gets rolled up into either a static constructor (If not the entry point) or the entry point method. The module's let bindings are placed in another class where value bindings are defined as properties and function bindings are defined as, well, methods. So you can see, as mentioned in the docs, that a do binding is not required when writing top level code in a module (The exception to this is if your writing a GUI app. You will need to use a do binding so that you can apply the STAThread attribute to it). The second thing to note is that it's the code in the last module that is compiled as the entry point for the application (If its not a library). And the entry point code does not need to be in a do binding (Unless it's a GUI app as mentioned above).

If it is a GUI app you can use the do binding to define the STAThread attribute as follows:

open System
open System.Windows.Forms

[<STAThread>]
do
    let form = new Form()
    Application.Run(form)

Which gets compiled to:

internal static class Startup
{
    [STAThread]
    public static void $main@()
    {
        Form form = new Form();
        Application.Run(form);
    }
}

All in all I don't see any point in using a do binding directly in a module unless you need to apply an attribute. If you, dear reader, have any insights into where a do binding in a module could be useful (Aside from applying attributes) I'd love to hear about it.

In a Type

As the docs mention, you can use the do binding in a type. Using a do binding in a type is more interesting as it forms the type constructor (Either static or instance) and, unlike a within module, is required if you want to define any initialization logic (Other than let bindings which form the private fields). Here is an example (And yes, I know this is a contrived example as we could have just done the assignment in the let binding):

namespace LawnCare
    open System

    type WeedWacker (manufacturer : string) =

        static let mutable random = null;
        let mutable maker = manufacturer
        let mutable sn = null

        static do
            random <- new Random()

        do
            sn <- maker.ToUpper() + "-" + random.Next().ToString()

        do
            maker <- maker.ToLower()

        member x.SerialNumber = sn
        member x.Maker = maker

This code gets compiled to the following:

internal static class $LawnCare
{
    static $LawnCare()
    {
        WeedWacker.random = null;
        WeedWacker.random = new Random();
    }
}
 
public class WeedWacker
{
    internal string sn;
    internal string maker;
    internal static Random random;

    public WeedWacker(string manufacturer)
    {
        maker = manufacturer;
        sn = null;
        sn = maker.ToUpper() + " " + random.Next();;
        maker = maker.ToLower();
    }

    public string SerialNumber { get { return sn; } }
    public string Maker { get { return maker; } }
}

Interesting to note that the type and its static constructor are separated into two different types. Also, just as with a module, you can define multiple do bindings and they will get rolled up into one instance and/or static constructor.

Not the most exciting topic I must admit, but good to know none the less.