1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

プログラミング Elixir 第五章

Last updated at Posted at 2016-10-20

概要

プログラミング Elixir

無名関数について解説している章。
無名関数とは、名前をつけずに関数自体を変数に束縛して、それを呼び出す方法。

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_onen に 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
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?