hoomin.kani

KANISAN WEB

インフラ・サーバーサイドを頑張るカニが学んだあれこれ。

Elixirのイミュータブルなデータ構造って?

f:id:hoominkani:20190530182301j:plain

イミュータブルとは?

あの言語はイミュータブルで、あっちはミュータブルで……という話をちらほら聞くことがありますが、そもそもイミュータブルとは何でしょうか?

直訳すると、

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って今どうなってるんだっけ?」といちいち確認する時間を大幅にカットすることができます。

しかし、イミュータブルであることによってPythonJavaScriptではできていたような処理ができなくなる、というのは一部エンジニアにとっては非常に不便と捉えられることもあるでしょう。

Elixirのイミュータブルでかつ再束縛可能なデータ構造は、その2つのメリットをうまく共存させたものだと言えそうです。