Dec 7, 2010

mutable binding and ref type

We all know that in F#, once you bind a value to an identifier, that the value can not be changed. What does this means? It means the your identifier will be like a read only property, the property return a value that is determined at the time of binding. After binding, the identifier looks like a constant. Mutable makes identifier more like a variable. But actually, it is a property with both getter and setter. Let's look at the following code.

let testMutable =
    //let mutable temp = 1
    let temp = 1
    let innerFunction() = 
        printfn "%i" temp
        ()
    innerFunction

let t1 = testMutable
t1()

This code compiles, but if we use mutable temp, then it shows an error like,



The mutable variable 'temp' is used in an invalid way. Mutable variables cannot be captured by closures. Consider eliminating this use of mutation or using a heap-allocated mutable reference cell via 'ref' and '!'.


So why? Identifier defined as mutable are limited in that they can not be used within a inner function like above. But still why? So let's decompile into c# and see that is going on?


public static void main@()
{
    int temp = 1; //because temp cannot be property
    FSharpFunc<Unit, Unit> innerFunction = new Program.innerFunction@161(temp);
    FSharpFunc<Unit, Unit> testMutable = testMutable@158 = innerFunction;
    FSharpFunc<Unit, Unit> t1 = t1@165 = Program.testMutable;
    Program.t1.Invoke(null);
}

[CompilationMapping(SourceConstructFlags.Value)]
public static FSharpFunc<Unit, Unit> testMutable
{
    get
    {
        return $Program.testMutable@158;
    }
} 

[Serializable]
internal class innerFunction@161 : FSharpFunc<Unit, Unit>
{
    // Fields
    public int temp;

    // Methods
    internal innerFunction@161(int temp) //innefunction accept value from parent function as constructor parameter
//it looks like a read/write property is not sufficient enough to support the 
// closure capture, 
    {
        this.temp = temp;
    }

 public override Unit Invoke(Unit unitVar0)
 {
        FSharpFunc<int, Unit> func = ExtraTopLevelOperators.PrintFormatLine<FSharpFunc<int, Unit>>(new PrintfFormat<FSharpFunc<int, Unit>, TextWriter, Unit, Unit, int>("%i"));
        int temp = this.temp; //copy the member to local variable
        func.Invoke(temp);
        return null;
    }
}

Let's change it to ref, as the error message suggested, and let's add some code to change the value of identifier because we can, then decompile it.


let testMutable =
    let temp = ref 1
    let innerFunction() = 
        temp := !temp  + 1
        printfn "%i" !temp
        ()
    innerFunction

let t1 = testMutable
t1() //print 2
t1() //print 3

public static void main@()
{
    FSharpFunc<Unit, Unit> innerFunction = new Program.innerFunction@161(Operators.Ref<int>(1));
    FSharpFunc<Unit, Unit> testMutable = testMutable@158 = innerFunction;
    FSharpFunc<Unit, Unit> t1 = t1@166 = Program.testMutable;
    Program.t1.Invoke(null);
    Program.t1.Invoke(null);
}

[CompilationMapping(SourceConstructFlags.Value)]
public static FSharpFunc<Unit, Unit> testMutable
{
    get
    {
        return $Program.testMutable@158;
    }
}
 
[Serializable]
internal class innerFunction@161 : FSharpFunc<Unit, Unit>
{
    // Fields
    public FSharpRef<int> temp;

    // Methods
    internal innerFunction@161(FSharpRef<int> temp)
    {
        this.temp = temp;
    }

    public override Unit Invoke(Unit unitVar0)
    {
//we can change the value, because it is ref Type(f#),

        Operators.op_ColonEquals<int>(this.temp, Operators.op_Dereference<int>(this.temp) + 1);

        FSharpFunc<int, Unit> func = ExtraTopLevelOperators.PrintFormatLine<FSharpFunc<int, Unit>>(new PrintfFormat<FSharpFunc<int, Unit>, TextWriter, Unit, Unit, int>("%i"));
//get the value from ref Object using op_Dereference operator       
int num = Operators.op_Dereference<int>(this.temp); 
        func.Invoke(num);
        return null;
    }
}

We can say to implement the closure feature (inner function can memorize mutable value from parent function), simple mutable value is not robust enough to support that. We need a ref Type. The ref type here is not the reference type we talk about in CLR or C#. The ref Type is generic record type. We can mimic ref implementation as follow, here we rename ref as wrapper.


type wrapper<'a> = { mutable innerValue: 'a }

let testref =
    let x = { innerValue = 1 }
    let innerFunction() =
        x.innerValue <- x.innerValue + 1
        printfn "%i" x.innerValue
    innerFunction

let f = testref
f()
f()

//decompiled into 
public static void main@()
{
    Program.wrapper<int> x = new Program.wrapper<int>(1);
    FSharpFunc<Unit, Unit> innerFunction = new Program.innerFunction@138(x);
    FSharpFunc<Unit, Unit> testref = testref@135 = innerFunction;
    FSharpFunc<Unit, Unit> f = f@142 = Program.testref;
    Program.f.Invoke(null);
    Program.f.Invoke(null);
}

[Serializable, CompilationMapping(SourceConstructFlags.RecordType)]
public sealed class wrapper<a> : IEquatable<Program.wrapper<a>>, IStructuralEquatable, IComparable<Program.wrapper<a>>, IComparable, IStructuralComparable
{
    // Fields
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    public a innerValue@;

    // Methods
    public wrapper(a innerValue);
    [CompilerGenerated]
    public sealed override int CompareTo(Program.wrapper<a> obj);
    [CompilerGenerated]
    public sealed override int CompareTo(object obj);
    [CompilerGenerated]
    public sealed override int CompareTo(object obj, IComparer comp);
    [CompilerGenerated]
    public sealed override bool Equals(Program.wrapper<a> obj);
    [CompilerGenerated]
    public sealed override bool Equals(object obj);
    [CompilerGenerated]
    public sealed override bool Equals(object obj, IEqualityComparer comp);
    [CompilerGenerated]
    public sealed override int GetHashCode();
    [CompilerGenerated]
    public sealed override int GetHashCode(IEqualityComparer comp);

    // Properties
    [CompilationMapping(SourceConstructFlags.Field, 0)]
    public a innerValue { get; set; }
}

 
[Serializable]
internal class innerFunction@138 : FSharpFunc<Unit, Unit>
{
    // Fields
    public Program.wrapper<int> x;

    // Methods
    internal innerFunction@138(Program.wrapper<int> x)
    {
        this.x = x;
    }

    public override Unit Invoke(Unit unitVar0)
    {
        this.x.innerValue = this.x.innerValue@ + 1;
        FSharpFunc<int, Unit> func = ExtraTopLevelOperators.PrintFormatLine<FSharpFunc<int, Unit>>(new PrintfFormat<FSharpFunc<int, Unit>, TextWriter, Unit, Unit, int>("%i"));
        int num = this.x.innerValue@;
        return func.Invoke(num);
    }
}

Of course, ref provide "!" operator instead of v.innerValue, and ":=" operator in place of "v.innerValue <- x", it is much more elegant. Once we understand ref, we can use it to create function using closure, which is really really powerful.