Search code examples
data-structuresf#mutable

F# record: ref vs mutable field


While refactoring my F# code, I found a record with a field of type bool ref:

type MyType =
  {
    Enabled : bool ref
    // other, irrelevant fields here
  }

I decided to try changing it to a mutable field instead

// Refactored version
type MyType =
  {
    mutable Enabled : bool
    // other fields unchanged
  }

Also, I applied all the changes required to make the code compile (i.e. changing := to <-, removing incr and decr functions, etc).

I noticed that after the changes some of the unit tests started to fail. As the code is pretty large, I can't really see what exactly changed.

Is there a significant difference in implementation of the two that could change the behavior of my program?


Solution

  • You have the difference not because one is first-value, passed by reference/value or other things. It's because a ref is just a container (class) on its own.

    The difference is more obvious when you implement a ref by yourself. You could do it like this:

    type Reference<'a> = {
        mutable Value: 'a
    }
    

    Now look at both definitions.

    type MyTypeA = {
        mutable Enabled: bool
    }
    
    type MyTypeB = {
        Enabled: Reference<bool>
    }
    

    MyTypeA has a Enabled field that can be directly changed or with other word is mutable.

    On the other-side you have MyTypeB that is theoretically immutable but has a Enabled that reference to a mutable class.

    The Enabled from MyTypeB just reference to an object that is mutable like the millions of other classes in .NET. From the above type definitions, you can create objects like these.

    let t = { MyTypeA.Enabled = true }
    let u = { MyTypeB.Enabled = { Value = true }}
    

    Creating the types makes it more obvious, that the first is a mutable field, and the second contains an object with a mutable field.

    You find the implementation of ref in FSharp.Core/prim-types.fs it looks like this:

        [<DebuggerDisplay("{contents}")>]
        [<StructuralEquality; StructuralComparison>]
        [<CompiledName("FSharpRef`1")>]
        type Ref<'T> = 
            { 
              [<DebuggerBrowsable(DebuggerBrowsableState.Never)>]
              mutable contents: 'T }
            member x.Value 
                with get() = x.contents
                and  set v = x.contents <- v
    
        and 'T ref = Ref<'T>
    

    The ref keyword in F# is just the built-in way to create such a pre-defined mutable Reference object, instead that you create your own type for this. And it has some benefits that it works well whenever you need to pass byref, in or out values in .NET. So you should use ref. But you also can use a mutable for this. For example, both code examples do the same.

    With a reference

    let parsed =
        let result = ref 0
        match System.Int32.TryParse("1234", result) with
        | true  -> result.Value
        | false -> result.Value
    

    With a mutable

    let parsed =
        let mutable result = 0
        match System.Int32.TryParse("1234", &result) with
        | true  -> result
        | false -> result
    

    In both examples you get a 1234 as an int parsed. But the first example will create a FSharpRef and pass it to Int32.TryParse while the second example creates a field or variable and passes it with out to Int32.TryParse