Jul 4, 2011

f# essential - part 5 - mutable, ref

As mentioned in previous post, the "let" syntax define a identifier binding, which is immutable. So it is not variable in the sense of traditional programming. But we can not programming in pure functional language environment. We need to create state, and side effect. F# is pragmatic and play nice with .net framework, so it has to be support variable. To use variable, you just need to add a "mutable" in front identifier, then the identifier became variable in the sense of traditional programming.

let totalArray2 () =
    let array = [|1; 2; 3|]
    let mutable total = 0
    
    for x in array do
        total <- total + x

    printfn "total: %i" total

totalArray2()

However, mutable does not support closure. So if you write the following code, you will get a compiler error.

let totalArray2 () =
    let array = [|1; 2; 3|]
    let mutable total = 0
    
    let sum() = 
        for x in array do
            total <- total + x

    sum()
    printfn "total: %i" total

//The mutable variable 'total' 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 '!'. 

The reason is that closure is bound to identifier but not variable. This identifier need to be immutable. To get around this, we can use immutable record identifier, but a member is mutable.

type MyRefRec<'a> = { mutable myref: 'a }
let totalArray1 () =
    let array = [|1; 2; 3|]
    let total = { myref = 0 }

    let sum() =
        for x in array do
            //now total is not a mutable type, so it can 
            //be captured by closure
            total.myref <- total.myref + x

    sum()

    printfn "total: %i" total.myref

You can see, MyRefRec is generic record. It has only one field. F# provide a shortcut and operator to simplified the above steps. This is "ref". So the above code can not be simplified as below.

let totalArray4 () =
    let array = [|1; 2; 3|]
    let total = ref 0

    let sum() =
        for x in array do
            //now total is ref type 
            total := !total + x

    sum()

    printfn "total: %i" !total