Elixirにおいて、匿名関数を使う場面は多々あります。
それぞれの書き方の利用例、メリット、デメリットを挙げていきます。
また、関数の匿名関数化やパイプライン演算子との複合についても触れていきます
環境
$ elixir --version
Erlang/OTP 23 [erts-11.0.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]
Elixir 1.10.3 (compiled with Erlang/OTP 23)
匿名関数宣言のシンタックス
1. fn ... end 記法
基本的なものとして挙げるなら、 fn .. end
記法でしょう。
匿名関数を学ぶ上で最初に書くものだと思います。
利用例1(単純な引数宣言)
double = fn num -> num * 2 end
# 単純な呼び出し
double.(3)
# => 6
# Enum.map/2と合わせる
Enum.map([1, 2, 4, 8], double)
# => [2, 4, 8, 16]
利用例2(引数でパターンマッチングを行う)
# 要素数が2の配列を受け取り、2つの位置を入れ替える
swap = fn [a, b] -> [b, a] end
swap.([1, 2])
# => [2, 1]
# caseのように、パターンマッチングによって処理を変える
reverse =
fn
[a, b] -> [b, a]
[a, b, c] -> [c, b, a]
end
reverse.([1, 2])
# => [2, 1]
reverse.([1, 2, 3])
# => [3, 2, 1]
パターンマッチングについては、Elixir Schoolを参照してください。
def ... end
での関数宣言をするほどではないが、細かいことをしたい場合にとても有効です。
利用例3(データ型のパターンマッチングを行う)
また、Map
やTuple
などのデータ型配列を扱う場合には効力を発揮します。
# Map
user1 = %{name: "alice", age: 15}
user2 = %{name: "bob" , age: 19}
user3 = %{name: "carol", age: 23}
users = [user1, user2, user3]
user_names = Enum.map(users, fn %{name: name} -> name end)
# => ["alice", "bob", "carol"]
# Tuple
result1 = {:ok, 1}
result2 = {:ok, 2}
result3 = {:ok, 3}
results = [result1, result2, result3]
Enum.map(results, fn {:ok, num} -> num end)
# => [1, 2, 3]
特定のキーのみ利用したい場面でリーダブルに書くことができます。
メリット
- 後述する
&(...)
記法よりも引数名が明示的となる - 複数行に渡る処理を書きやすい
- 引数でのパターンマッチングが行える
デメリット
- 単純に引数を並べるだけなら、
&(...)
記法のほうが簡潔
2. &(...) 記法(キャプチャ演算子)
単純な引数を取る、インライン宣言であればこの記法を利用するのがベストでしょう。
引数値、引数の順番がぱっと見で分かりづらいため、多用するべきではないと思っています。コメントが必要ないほどの短いコードでなら問題はないです。
引数を渡した順にプレースホルダへとバインディングされます。
利用例1
double = &(&1 * 2)
double.(3)
# => 6
利用例2(データ型配列の単純な利用)
user1 = %{name: "alice", age: 15}
user2 = %{name: "bob" , age: 19}
user3 = %{name: "carol", age: 23}
users = [user1, user2, user3]
user_names = Enum.map(users, &(&1.name))
# => ["alice", "bob", "carol"]
メリット
- 引数宣言がないため簡潔に書ける
- 単純な処理なら可読性が上がる
デメリット
- 複数行での処理に向かない
- fn ... end 記法と違い、引数のパターンマッチングが行えない
3. キャプチャ演算子による関数の匿名関数化
匿名関数を引数にとる関数には、関数を渡す場面もあります。
上記であげた fn ... end
, &(...)
記法では冗長であるため、それを解決するのが以下の記法です。
&to_string/1
のように、&に続いて関数名/アリティと記述することで匿名関数化を行えます。
また、プレースホルダを利用することも可能です。
利用例1
# 以下の全てのコードは等価
# &(...) 記法
Enum.map([1, 2, 3, 5], &(to_string(&1)))
# => ["1", "2", "3", "5"]
# アリティ付きで渡す
Enum.map([1, 2, 3, 5], &to_string/1)
# プレースホルダを引数にする
Enum.map([1, 2, 3, 5], &to_string(&1))
利用例2(部分適用した匿名関数を渡す)
&Kernel.+/2
など、アリティ1を引数にとる関数に渡す場合は部分適用(カリー化)することで呼び出すことができます。
部分適用についてはこの記事の本質ではないため、説明はしません。
# &(...) 記法
add_two = &(2 + &1)
# 部分適用
add_two = &Kernel.+(2, &1)
add_two.(3)
# => 5
Enum.map([1, 2, 4, 8], add_two)
# => [3, 4, 6, 10]
4. パイプライン演算子と組み合わせる
ここまでは匿名関数の宣言と利用例を挙げていきました。
Elixirらしく、パイプライン演算子と組み合わせてみましょう。
パイプライン演算子で匿名関数を呼び出すには、通常関数の呼び出しと同様に第一引数を省略することと変数名.()
で呼び出せます。
double = &(&1 * 2)
3 |> double.()
# => 6
# パイプライン演算子に直接匿名関数を渡す場合は()で括り、実引数を渡す
3 |> (&(&1*2)).()
5 |> (&(&1*&2)).(2)
# => 10
利用例
require Integer
double = &(&1 * 2)
triple = &(&1 * 3)
number =
1..10
|> Enum.random
number
|> Integer.is_odd
|> (fn
# 奇数なら3倍
true , num -> triple.(num)
# 偶数なら2倍
false, num -> double.(num)
end).(number)
|> to_string
まとめ
匿名関数をうまく扱うことでリーダブルになることが実感できたと思います。
Elixirで書くなら切っても切れないので、しっかり身に付けましょう