1. Over View
どもども、Elixirチートシートを作ろう、番外編その1であります
Elixir イミュータブルについて、誤解していたと思うのでまとめてみました。
2. さて、本題
変数は、僕の知る他の言語のように変数に割り当てられたメモリが書き換わらないだけ、と考えていたのですが、大きな考え違いをしている気がしてきました。
イミュータブルなのは、変数ではなくて、値自体だと考え直しました。
なので、普通のプログラミング言語のように、a = a + 1のような処理はできますが、これは、aのバインド先が変わるだけなので、どんどんメモリ上に結果が1, 2, 3, ...と増えていきます。
例1)
行番号 | 命令 |
---|---|
1 | A = 1 |
2 | A = A + 1 |
この場合、1行目で、Aと1という値がバインドされる。2行目で、A + 1の結果である2とバインドされる。
C言語のように、変数Aのために確保されたメモリ領域が書き換わるわけではない。
ここまでで、ずいぶんと非効率だな、と頭を悩ませていたのですが、値はすべて書き換わらないのだから、これはリストなどを処理するのには、逆に効率が良い。
例2)
[2, 3] -> この値の先頭に、1を追加する場合は、以下のように書きます。
iex(23)> b = [2, 3]
[2, 3]
iex(24)> b = [1] ++ b
[1, 2, 3]
こいつを命令単位にバラすと、以下の様になります。
行番号 | 命令 |
---|---|
1 | b = [2, 3] |
2 | b = [1] ++ b |
※List.insert_at(b, 0, 1)でも一緒
1行目の処理を終えた後、2行目では、変数bに、新たに1, 2, 3というリストをコピーすると考えて、非常に効率が悪い気がしていたのですが、実際には、リスト[2, 3]の先頭に要素1を加えたリストにバインドするだけだと気づきました。
変更前のbのリスト型の構造
変数 | 中身 |
---|---|
b | [2, 3] |
bのhd | 2にバインドされている |
bのtl | 3にバインドされている |
変更後のbのリスト型の構造
計算の右辺の結果が、以下のリストとして作られる。
変数 | 中身 |
---|---|
hd | 1にバインドされる |
tl | [2, 3](今までのb)にバインド |
b = [1] ++ bと書いているので、このリストが、bにバインドされ直す。
変数 | 中身 |
---|---|
b | [1, 2, 3] |
要するに、LISTの中身をbindしている先を切り替えるだけで、別にlistを再作成しているわけでもない。
イミュータブルなおかげで、少ない手数でリストがコピーできるので、ガンガン、 b = [1] ++ bと書いてよいのだな開き直りました。
補足)ついでに、Elixirでは、リストの先頭に挿入する方が、コストが安い。