F Sharp Programming/Mutable Data

All of the data types and values in F# seen so far have been immutable, meaning the values cannot be reassigned another value after they've been declared. However, F# allows programmers to create variables in the true sense of the word: variables whose values can change throughout the lifetime of the application.

Keyword
The simplest mutable variables in F# are declared using the  keyword. Here is a sample using fsi: As shown above, the  operator is used to assign a mutable variable a new value. Notice that variable assignment actually returns  as a value.

The mutable keyword is frequently used with record types to create mutable records:

Before process: {ID = 0; IsProcessed = false; ProcessedText = null;} {ID = 1; IsProcessed = false; ProcessedText = null;} {ID = 2; IsProcessed = false; ProcessedText = null;} {ID = 3; IsProcessed = false; ProcessedText = null;} {ID = 4; IsProcessed = false; ProcessedText = null;} After process: {ID = 0; IsProcessed = true; ProcessedText = "Processed 10:00:31";} {ID = 1; IsProcessed = true; ProcessedText = "Processed 10:00:32";} {ID = 2; IsProcessed = true; ProcessedText = "Processed 10:00:33";} {ID = 3; IsProcessed = true; ProcessedText = "Processed 10:00:34";} {ID = 4; IsProcessed = true; ProcessedText = "Processed 10:00:35";}

Limitations of Mutable Variables
Mutable variables are somewhat limited: before F# 4.0, mutables were inaccessible outside of the scope of the function where they are defined. Specifically, this means its not possible to reference a mutable in a subfunction of another function. Here's a demonstration in fsi:

Ref cells
Ref cells get around some of the limitations of mutables. In fact, ref cells are very simple datatypes which wrap up a mutable field in a record type. Ref cells are defined by F# as follows:

The F# library contains several built-in functions and operators for working with ref cells:

The  function is used to create a ref cell, the   operator is used to read the contents of a ref cell, and the   operator is used to assign a ref cell a new value. Here is a sample in fsi:

Since ref cells are allocated on the heap, they can be shared across multiple functions:

The  function has the type.

This program outputs the following: hello world assigned from withSideEffects function

The  function is named as such because it has a side-effect, meaning it can change the state of a variable in other functions. Ref Cells should be treated like fire. Use it cautiously when it is absolutely necessary but avoid it in general. If you find yourself using Ref Cells while translating code from C/C++, then ignore efficiency for a while and see if you can get away without Ref Cells or at worst using mutable. You would often stumble upon a more elegant and more maintanable algorithm

Aliasing Ref Cells

 * Note: While imperative programming uses aliasing extensively, this practice has a number of problems. In particular it makes programs hard to follow since the state of any variable can be modified at any point elsewhere in an application. Additionally, multithreaded applications sharing mutable state are difficult to reason about since one thread can potentially change the state of a variable in another thread, which can result in a number of subtle errors related to race conditions and dead locks.

A ref cell is very similar to a C or C++ pointer. Its possible to point to two or more ref cells to the same memory address; changes at that memory address will change the state of all ref cells pointing to it. Conceptually, this process looks like this:

Let's say we have 3 ref cells looking at the same address in memory:



,, and   are all pointing to the same address in memory. The  property of each cell is. Let's say, at some point in our program, we execute the code, this changes the value in memory to the following:



By assigning  a new value, the variables   and   were changed as well. This can be demonstrated using fsi as follows:

Encapsulating Mutable State
F# discourages the practice of passing mutable data between functions. Functions that rely on mutation should generally hide its implementation details behind a private function, such as the following example in FSI: