概要
無名関数について解説している章。
無名関数とは、名前をつけずに関数自体を変数に束縛して、それを呼び出す方法。
iex> sum = fn (a, b) -> a + b end
#Function<12.52032458/2 in :erl_eval.expr/5>
iex> sum.(1, 2)
3
例の様に 引数 -> 関数の実装(ボディ)
を fn ... end
で囲み定義する。
呼び出す際は、.()
で呼び出す。名前付き関数とは違い、.
, ()
が必要となる。
関数定義の際には ()
は省略可能である。
iex> sum = fn a, b -> a + b end
#Function<12.52032458/2 in :erl_eval.expr/5>
iex> sum.(1, 2)
3
iex> say = fn -> IO.puts "hello" end
#Function<20.52032458/0 in :erl_eval.expr/5>
iex> say.()
hello
関数とパターンマッチ
sum.(1, 2)
と呼ぶと a
に 1、b
に 2 が代入されている様に感じるが、Elixir に代入は存在しない(プログラミング Elixir 第二章)。1 は a に代入ではなく、束縛され、パターンマッチが行われている。
以下の例を見るとわかりやすい。
iex> match? = fn (1, 2) -> "match" end
#Function<12.52032458/2 in :erl_eval.expr/5>
iex> match?.(1, 2)
"match"
iex> match?.(1, 3)
** (FunctionClauseError) no function clause matching in :erl_eval."-inside-an-interpreted-fun-"/2
関数に複数のボディを持たせる
Elixir は一つの関数に、異なる型・内容の引数、それに対応したボディを実装することができる。これとパターンマッチの機能を使って、便利な関数を実装する事ができる。
iex> fn
...> (pattern 1) -> hogehoge
...> (pattern 2) -> fugafuga
...> (pattern 3) -> piyopiyo
...> end
上記の様なイメージで、パターンの異なる引数とボディを実装する。この際、引数の数を変えることはできない。
実行した際に pattern 2
にマッチする値を渡した場合は fugafuga
が実行される。
以下は、この機能をうまく使った無名関数の例である。
iex> file_open = fn
...> {:ok, file} -> "Read data: #{IO.read(file, :line)}"
...> {_, error} -> "Error: #{:file.format_error(error)}"
...> end
#Function<6.52032458/1 in :erl_eval.expr/5>
iex> file_open.(File.open("./sample.txt"))
"Read data: Hello!!"
iex> file_open.(File.open("./not_exist.txt"))
"Error: no such file or directory"
無名関数を見てみると 2 つの引数が定義されている。一つ目のタプルは :ok
を第一要素に持ったタプル。二つ目のタプルは _
を第一要素に持ち、どんな値でもマッチする。
これによって File.open
の結果によって動作の異なる関数を実装することができる。
ここで、:file
は Erlang の File モジュールを参照していて、それによって format_error
メソッドを呼び出している。また、文字列で #{}
で囲むと式が評価される。
関数を返す関数
関数を返す関数を定義することもできる
iex> hello = fn -> fn -> "Hello" end end
#Function<20.52032458/0 in :erl_eval.expr/5>
iex> hello.()
#Function<20.52032458/0 in :erl_eval.expr/5>
iex> hello.().()
"Hello"
hello.()
で外側の関数が呼ばれ、内側の関数を返す。
そして、返ってきた内側の関数が .()
で実行され、Hello
を出力している。
書き方は改行とインデントを入れたり、内側の関数定義を括弧でくくることもできる。
iex> hello.().()
"Hello"
iex> hello = fn ->
...> fn ->
...> "Hello"
...> end
...> end
#Function<20.52032458/0 in :erl_eval.expr/5>
iex> hello = fn -> (fn -> "Hello" end) end
#Function<20.52032458/0 in :erl_eval.expr/5>
束縛を引き継ぐ
以下は外側で束縛した値を内側の関数が引き継いでいる例
iex> greeter = fn name -> (fn -> "Hello #{name}" end) end
#Function<6.52032458/1 in :erl_eval.expr/5>
iex> dave_greeter = greeter.("dave")
#Function<20.52032458/0 in :erl_eval.expr/5>
iex> dave_greeter.()
"Hello dave"
このように外側で束縛した値は、内側の関数の変数にも束縛されていて、後から呼び出すことができる。
これを利用することで以下のような関数を作ることができる。
iex> add = fn n -> (fn m -> n + m end) end
#Function<6.52032458/1 in :erl_eval.expr/5>
iex> add_one = add.(1)
#Function<6.52032458/1 in :erl_eval.expr/5>
iex> add_three = add.(3)
#Function<6.52032458/1 in :erl_eval.expr/5>
iex> add_one.(2)
3
iex> add_three.(2)
5
add_one
は n
に 1 が束縛されているので、引数に 1 を足す関数。
そして、add_three
n
に 3 が束縛されているので、引数に 3 を足す関数となる。
関数を引数として使う
これまで見てきたように関数はただの型の一つなので、引数として使用できる。
iex> divide_half = fn n -> n / 2 end
#Function<6.52032458/1 in :erl_eval.expr/5>
iex> executor = fn (func, value) -> func.(value) end
#Function<12.52032458/2 in :erl_eval.expr/5>
iex> executor.(divide_half, 6)
3.0
2 で割る関数を executor に渡して、executor は第二引数の value を func に渡して実行している。
引数にピン演算子を使う
ピン演算子を変数につけるとパターンで使うことができるが、ピン演算子は引数にも使用できる。
iex> greet = fn name, greeting ->
...> fn
...> (^name) -> "#{greeting} #{name}"
...> (_) -> "I don't know you"
...> end
...> end
#Function<12.52032458/2 in :erl_eval.expr/5>
iex> hello = greet.("Tom", "Hello")
#Function<6.52032458/1 in :erl_eval.expr/5>
iex> hello.("Tom")
"Hello Tom"
iex> hello.("Mike")
"I don't know you"
greet.("Tom", "Hello")
で name
に "Tom"
を束縛して、その後はパターンマッチを行っている。マッチした時のみ一行目が実行される。
& 記法
fn () -> ... end
は & 記法を使って短く書くこともできる
iex> add_one = &(&1 + 1) # fn (n) -> n + 1 end
#Function<6.52032458/1 in :erl_eval.expr/5>
iex> add_one.(2)
3
引数を増やしたい場合は &1, &2, &3 ... としていく
また、Elixir は既にある関数を呼び出すだけの無名関数を作成した場合、無名関数を作らずに直接参照する様に最適化する。
iex> add = &(&1 + &2)
&:erlang.+/2
iex> add.(1, 2)
3
ただし、引数をそのままの順番で渡す必要がある。
iex> add = &(&2 + &1)
#Function<12.52032458/2 in :erl_eval.expr/5>
また、リストやタプルのリテラルを関数に変換する事もできる。
以下は和と差を返す関数。
iex> add_and_diff = &{ (&1 + &2), (&1 - &2) }
#Function<12.52032458/2 in :erl_eval.expr/5>
iex> add_and_diff.(3, 1)
{4, 2}
iex> add_and_diff = &[ (&1 + &2), (&1 - &2) ]
#Function<12.52032458/2 in :erl_eval.expr/5>
iex> add_and_diff.(3, 1)
[4, 2]
また、& 記法は既に存在する関数の名前とアリティ(パラメーター数)を渡すと、それを呼び出す無名関数を返す。
iex> add = &+/2
&:erlang.+/2
iex> add.(1, 2)
3
iex> l = &length/1
&:erlang.length/1
iex> l.([1, 2, 3, 4])
4