What is the difference between member val and member this in F#?

310 views Asked by At

When I created a class containing a generic, mutable .NET Stack in F# like the example below, that stack ignores anything I push onto it.

open System.Collections.Generic

type Interp(code: int array) =
    member val PC = 0 with get, set
    member this.stack: Stack<int> = new Stack<int>() 
    member this.Code = code

let interp = Interp([|1;2;3|])
interp.stack.Push(1)
printfn "%A" interp.stack // prints "seq[]" WAT?!

Yet if I make the stack mutable via a property:

open System.Collections.Generic

type Interp(code: int array) =
    member val PC = 0 with get, set
    member val stack: Stack<int> = new Stack<int>() with get, set 
    member this.Code = code

let interp = Interp([|1;2;3|])
interp.stack.Push(1)
printfn "%A" interp.stack // prints "seq[1]"

Everything magically works like I'd expect.

What on earth is going on here? My understanding of immutability from previous languages (C# mostly) would say that even though the stack in the first example is an immutable member, that immutablity should only go as far the reference (aka I shouldn't be able to reassign Stack itself). I should still be able to push values to/from it. What am I missing, and if trying to mutate that stack is the wrong thing, why doesn't it throw an exception or a compile error?

2

There are 2 answers

0
Mark Seemann On BEST ANSWER

If you try to compile the first version, and then use e.g. Reflector to decompile it to C#, you'll see that the stack member is defined like this:

public class Interp
{
    public Stack<int> stack
    {
        get { return new Stack<int>(); }
    }

    // Other members omitted for clarity...
}

As you can see, this is also valid C# code, but obviously not what you want.

The second version cross-compiles to something like this:

public class Interp
{
    internal int[] code;
    internal Stack<int> stack@;

    public Interp(int[] code) : this()
    {
        this.code = code;
        this.stack@ = new Stack<int>();
    }

    public Stack<int> stack
    {
        get { return this.stack@; }
        set { this.stack@ = value; }
    }

    // Other members omitted for clarity...
}

This seems more like what you'd want a property to do.

5
phoog On

A more idiomatic way to do what you want is this:

open System.Collections.Generic

type Interp(code: int array) =
    let stack = Stack<int>()
    member val PC = 0 with get, set
    member this.Stack = stack
    member this.Code = code

If you don't need to expose the stack externally, omit the next to last line.