When should I use let, member val and member this.?

The answer from @meziantou already gives a nice overview of the options (and how they behave differently), so let me just give a brief summary, or list of recommendations:

  • Use let or let mutable if you want to define a local value that is visible only within the type (essentially a private field or a private function). Inside a module at top-level, these are publicly accessible and evaluated once. let mutable at module level creates a single writable field with no backing value.

  • You can use val to create an auto-property, it is short for member val Foo = .. with get. From F# this is seen as a field, but it's internally implemented as a get-property with a backing field to prevent mutation.

  • You can use val mutable to define a public field, but I wouldn't recommend this unless you actually need a public field (e.g. some .NET library may require types with this structure).

  • Using member x.Foo = ... is the best way to expose (read-only) state from a type. Most F# types are immutable, so this is perhaps the most common public member. It is short for a get-only instance property.

  • Using member x.Foo with get() = .. and set(value) ... is useful when you need to create a get/set property with your own custom code in the gettor and settor. This is sometimes useful when you're creating a mutable object.

  • Using member val Foo = ... with get, set is basically the same thing as auto-implemented properties in C#. This is useful if you need a mutable property with a getter and setter that just reads/writes a mutable backing field.

  • Using static let on a type creates a static (class-level) read-only field, which internally creates a property with a backing field. Use static mutable let ... for a read/write static field without a backing field.

  • Using static val mutable private creates a static read/write auto-property with a backing field, it cannot be public.


I found out easier to just decompile what's happening, so:

type Region() =
  let mutable t = 0.0f
  member val Width = 0.0f
  member x.Height = 0.0f
  member val Left = 0.0f with get,set
  member x.Top with get() = 0.0f and set(value) = t <- value

is actually the following:

public class Region
{
    internal float t;

    internal float Width@;

    internal float Left@;

    public float Width
    {
        get
        {
            return this.Width@;
        }
    }

    public float Height
    {
        get
        {
            return 0f;
        }
    }

    public float Left
    {
        get
        {
            return this.Left@;
        }
        set
        {
            this.Left@ = value;
        }
    }

    public float Top
    {
        get
        {
            return 0f;
        }
        set
        {
            this.t = value;
        }
    }

    public Region() : this()
    {
        this.t = 0f;
        this.Width@ = 0f;
        this.Left@ = 0f;
    }
}

This sample explains the difference between syntaxes:

type MyClass() =
    let random  = new System.Random() 
    [<DefaultValue>] val mutable field : int
    member val AutoProperty = random.Next() with get, set
    member this.ExplicitProperty = random.Next()

let c = new MyClass()
// c.random is not accessible
c.field <- 42 // 'field' is accessible

// An automatic property is only evaluated upon initialization, and not every time the property is accessed
printfn "AutoProperty = %d" c.AutoProperty // x
printfn "AutoProperty = %d" c.AutoProperty // Still x

// The value of the explicit property is evaluated each time
printfn "ExplicitProperty = %d" c.ExplicitProperty // y
printfn "ExplicitProperty = %d" c.ExplicitProperty // The value is re-evaluated so you'll get a different value