F# has many different ways to define variables/members in types. When should I use let
, member val
and member this.
in F#, and what is the difference between them? How about static and mutable members?
3 Answers
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
orlet mutable
if you want to define a local value that is visible only within the type (essentially aprivate
field or aprivate
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 formember 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. Usestatic 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