Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Writing to mutable property for a struct record is not allowed in F#. Why?

Tags:

f#

When I have the following code:

[<Struct>]
type Person = { mutable FirstName:string ; LastName : string}

let john = { FirstName = "John"; LastName = "Connor"}

john.FirstName <- "Sarah";

The compiler complains that "A value must be mutable in order to mutate the contents". However when I remove the Struct attribute it works fine. Why is that so ?

like image 714
Onur Gumus Avatar asked Jul 03 '17 03:07

Onur Gumus


1 Answers

This protects you from a gotcha that used to plague the C# world a few years back: structs are passed by value.

Note that the red squiggly (if you're in IDE) appears not under FirstName, but under john. The compiler complains not about changing the value of john.FirstName, but about changing the value of john itself.

With non-structs, there is an important distinction between the reference and the referenced object:

enter image description here

Both the reference and the object itself can be mutable. So that you can either mutate the reference (i.e. make it point to a different object), or you can mutate the object (i.e. change the contents of its fields).

With structs, however, this distinction does not exist, because there is no reference:

enter image description here

This means that when you mutate john.FirstName, you also mutate john itself. They are one and the same.

Therefore, in order to perform this mutation, you need to declare john itself as mutable too:

[<Struct>]
type Person = { mutable FirstName:string ; LastName : string}

let mutable john = { FirstName = "John"; LastName = "Connor"}

john.FirstName <- "Sarah"  // <-- works fine now

For further illustration, try this in C#:

struct Person
{
    public string FirstName;
    public string LastName;
}

class SomeClass
{
    public Person Person { get; } = new Person { FirstName = "John", LastName = "Smith" };
}

class Program
{
    static void Main( string[] args )
    {
        var c = new SomeClass();
        c.Person.FirstName = "Jack";
    }
}

The IDE will helpfully underline c.Person and tell you that you "Cannot modify the return value of 'SomeClass.Person' because it is not a variable".

Why is that? Every time you write c.Person, that is translated into calling the property getter, which is just like another method that returns you a Person. But because Person is passed by value, that returned Person is going to be a different Person every time. The getter cannot return you references to the same object, because there can be no references to a struct. And therefore, any changes you make to this returned value will not be reflected in the original Person that lives inside SomeClass.

Before this helpful compiler error existed, a lot of people would do this:

c.Person.FirstName = "Jack"; // Why the F doesn't it change? Must be compiler bug!

I clearly remember answering this question almost daily. Those were the days! :-)

like image 160
Fyodor Soikin Avatar answered Nov 05 '22 11:11

Fyodor Soikin