弊社ではpythonを使った開発が主流ですが、ここ最近はelixirでの開発にも取り組んでおります。
そこでelixirの勉強をしているのですが、現時点で一番ハマったことを共有します。
問題
リスト[1, 2, 3, 4, 5]の各要素の合計を求めるプログラムを実装しなさい。
(ただし、Enum.sum()は使わない)
実装
pythonで実装したらどうなるかを考えて、それをelixirで書き直せばいいんじゃないかなと思った(のがそもそもの間違いだった)。
total = 0
for i in [1, 2, 3, 4, 5]:
total += i
print(total)
これをelixirで書くと・・・(なお、間違いです)
total = 0
for i <- [1, 2, 3, 4, 5] do
total = total + i
end
IO.puts total
結果
IO.puts total は0でした。
なぜ?
かなり回りくどい書き方になってしまいますが、私が理解した順番に記述します。
スコープの問題だった
for の中と外でスコープが異なるため、IO.puts totalのtotalは、forの外側のtotalでした。
湧き上がる疑問
じゃぁforの中ではちゃんとリストの要素を足してるんだろうなぁ。
total = 0
for i <- [1, 2, 3, 4, 5] do
total = total + i
IO.puts total
end
結果
(本気で「!?」でした)そもそもの誤解
for i <- [1, 2, 3, 4, 5] do
...
end
これはelixirではループではなく、[1, 2, 3, 4, 5]のリストから要素を取り出す、内包表記と呼ばれる記述方法でした。
なのでforの中で足し算するものの、毎回その結果は捨て去られている(という表現が適切かわかりませんが)。
ですので「スコープの問題だった」という見出しを作りましたが、根本的に間違っていたということになります。
で、elixirで実装するとしたら?
Enum.reduce()を使って
total = Enum.reduce([1, 2, 3, 4, 5], fn(x, acc) -> x + acc end)
IO.puts total
と書く感じでしょうか。
ちなみに封じていたEnum.sum()は?
気になったので見て見たらこのように定義されてました。
def sum(enumerable) do
reduce(enumerable, 0, &+/2)
end
&+/2 とは一体・・・
なんすかこれ!?と思って調べたところ・・・
&記法
elixirでは無名関数をよく使うため、省略記法として、&記法というものがあるそうです。
&以降の式を関数に変換する演算子とのこと。
なので、「&+/2」は「+/2」という無名関数・・・「+/2」とは?
+/2
実現させたいのは2つの数の和です。
それは下記のように書けます。
fn x, y -> x + y end
この無名関数は引数が2つあって、それの単純に足しています。
これをelixirは &+/2 と書けてしまうのだそうです。
(ちなみに「/2」という記述は「アリティが2つ」を意味してます。)
&+/2と書けてしまう、ではなく・・・
elixirではそもそも「+」という関数が定義されておりました。
https://hexdocs.pm/elixir/Kernel.html#+/2
引数2つの「+」という関数を渡す際に &+/2 と表記するようです。
話が脱線してしまいましたが・・・
せっかくelixirで便利な関数が用意されているのだから、使用を禁止せずに素直に使った方が楽ですね(当然ですよねw)。