Elixirのイミュータブルなデータ構造って?
イミュータブルとは?
あの言語はイミュータブルで、あっちはミュータブルで……という話をちらほら聞くことがありますが、そもそもイミュータブルとは何でしょうか?
直訳すると、
immutable = 不変の、変わらない
という意味になります。
プログラミングの世界では、オブジェクトが変更可能であることを「ミュータブルである」と定義し、それとは逆にオブジェクトへの破壊的変更が不可能である状態を「イミュータブルである」と定義しています。
Elixirにおけるイミュータブル
Elixirについて解説する多くのサイトでは
「Elixirはイミュータブルなデータ構造を持つ」
と述べられていますが、具体的にはどういうことなのでしょうか?
実際の例をみていきましょう。
まずは下記のコードをご覧ください。
iex(1)> i = 20 20 iex(2)> age = fn -> i end #Function<20.128620087/0 in :erl_eval.expr/5> iex(3)> age.() # iは20とマッチしている 20 iex(4)> i = 30 # iに30を再束縛 30 iex(5)> i # iは30とマッチしている 30 iex(6)> age.() #iは元の20を参照している 20
iが20とマッチした後、iに30が再代入されたかのように見えますが、Elixirの場合はそうではありません。
4行目で行われているのは、いわゆる再束縛(再バインド)と呼ばれ、イメージとしては「同じ名前で別の変数を作成している」処理になります。 ※ ちなみにErlangでは再束縛はできません。
そのおかげで、6行目で再び既存の関数を呼び出した際にはきちんと元の変数が参照されています。
このように、再束縛を行いながらも常に構造が保持されているため、Elixirのデータ構造はイミュータブルであると言えます。
再束縛を禁止したい場合
コードを書いていると、再束縛をするのではなく、単にパターンマッチを行いたい場合が出てくると思います。
そういった際はピン演算子を使い、以下のようにパターンマッチを行うことができます。
iex(1)> i = 1 1 iex(2)> ^i = 2 ** (MatchError) no match of right hand side value: 2
最後に: Elixir的イミュータブルのメリット
イミュータブルであることにより破壊的変更を阻止することができるため、プログラムのバグを防ぎ、実装する際に「これの変数iって今どうなってるんだっけ?」といちいち確認する時間を大幅にカットすることができます。
しかし、イミュータブルであることによってPythonやJavaScriptではできていたような処理ができなくなる、というのは一部エンジニアにとっては非常に不便と捉えられることもあるでしょう。
Elixirのイミュータブルでかつ再束縛可能なデータ構造は、その2つのメリットをうまく共存させたものだと言えそうです。