ミュータブル(可変)/イミュータブル(不変)オブジェクト
以前、F#は非純粋関数型言語であると説明しましたが、では非純粋関数型言語ではどのように値の状態を変更するかということについて説明します。
まず基本となる、イミュータブル、ミュータブルの概念から説明します。
既存の.NET言語の変数はほとんどデフォルトでミュータブルです。一方、F#のオブジェクトは基本的にデフォルトではイミュータブル(不変)であり、一度値が決められたらそれを変更することができません。
マルチスレッド環境では、ミュータブル(可変)なデータは、複数のスレッドによって変更される可能性があり、管理が難しいです。それに対し、イミュータブルなデータはコード内のプログラムとその機能を強力に結びつけるため、デバッグが簡単になる傾向があります。イミュータブルなデータを用いたプログラムができることは結果を戻すことのみなので、関数やメソッドにおいてインプット・アウトプットの分析などが簡単だからです。オブジェクトがイミュータブルである場合、オブジェクトの複製は参照の複製であり、メモリが節約できたり、プログラムが速く実行できるというメリットもあります。
また、データがイミュータブルなオブジェクトで表現されている場合、排他制御の心配をしなくてもよく、ミュータブルなオブジェクトより比較的スレッドセーフであると言えます。
ミュータブルなオブジェクトと参照セル
では、なぜF#にはオブジェクトをミュータブルなオブジェクトとして定義する機能があるのでしょうか。
それは、ミュータブル変数はプログラムの実行中に変数の値を変えることができるなど、時にプログラムに柔軟性を与えるからです。また、他.NET言語と相互運用するためにも、F#においてミュータブル変数は不可欠な機能でもあります。
オブジェクトをミュータブル変数として定義するにはmutableキーワードを使用します。
let mutable 変数名 = 値
正確には「変数」イコール「ミュータブル(可変)」なのですが、ここでは解説上分かりやすくするため、ミュータブル変数という表現を使用したいと思います。
以下の例では、xをミュータブル変数として宣言し、デフォルトの値1に10を追加しています。見慣れない“<-"は代入用の演算子で、"x + 10"をxに代入することを意味します。
let mutable x = 1 x <- x + 10 x;;
▼
val mutable x : int = 11
F#においてもう1つ、値を一度定義された後に変更する方法があります。参照セルと呼ばれるものです。一般的に他の言語でポインタとして知られる機能に似た機能です。ref演算子を使用して定義された値はコピーされ初期値となり、同時に値への参照が作成されます。つまり、参照セルはアドレスだけではなく、値も保持します。参照先の値が変化するたびに値のコピーが作成されます。
ミュータブル変数と参照セルは大変似た機能を持ちますが、クロージャを生成する式(ラムダ式、シーケンスなど)ではミュータブル変数は使用できず、参照セルのみ使用可能です(ミュータブル変数を定義しようとするとコンパイラはエラーを返します)。
let 変数名 = ref 値
参照セルの主要機能を解説します。
以下はmutableなオブジェクトと参照セルの機能の違いを比較する例です。
let mutable xMut = 3;; //※1 xMut <- xMut + 1;; //※2 xMut;; let mutable yMut = xMut;; //※3 yMut <- yMut + 1;; //※4 yMut;; xMut;;
▼
val mutable yMut : int = 4 > val it : unit = () > val it : int = 5 > val it : int = 4 val mutable xMut : int = 3 > val it : unit = () > val it : int = 4 >
リスト3と以下の図を見てください。

xMutというint型(値型)のミュータブル変数に3をバインド(※1)します。
xMutに1を追加すると、xMutは4になります(※2)。ミュータブル変数としてyMutにxMutをバインドすると、yMutもまたint型(値型)になり4という値がバインド(※3)されます。
ここで、yMutに1を追加する(※4)と、yMutは5になりますが、xMutへの操作は行われないため、xMutの値は4のままです。
