より
6.1 関数型っぽくいこう!
Elixir で無名関数を評価するときは f.()
という形式にする.
f()
ではなく .
がついた形であることに注意すること.
defmodule Hhfuns do
def one, do: 1
def two, do: 2
def add(x, y), do: x.() + y.()
end
Hhfuns.add(Hhfuns.one, Hhfuncs.two)
#> ** (BadFunctionError) expected a function, got: 1
#> orgmode_elixir_src.exs:5: Hhfuns.add/2
#> (elixir) lib/code.ex:307: Code.require_file/2
Hhfuns.add(1, 2)
#> ** (BadFunctionError) expected a function, got: 1
#> orgmode_elixir_src.exs:5: Hhfuns.add/2
#> (elixir) lib/code.ex:307: Code.require_file/2
Hhfuns.add(&Hhfuns.one/0, &Hhfuns.two/0) # => 3
関数をモジュールの外から渡すための新しい記述
Elixir だと &(モジュール).(関数)/(アリティ)
という形式になる.
defmodule Hhfuns do
def increment([]), do: []
def increment([h|t]), do: [h+1|increment(t)]
def decrement([]), do: []
def decrement([h|t]), do: [h-1|decrement(t)]
def map(_, []), do: []
def map(f, [h|t]), do: [f.(h)|map(f,t)]
def incr(x), do: x + 1
def decr(x), do: x - 1
end
l = [1,2,3,4,5]
Hhfuns.increment(l) # => [2, 3, 4, 5, 6]
Hhfuns.decrement(l) # => [0, 1, 2, 3, 4]
Hhfuns.map(&Hhfuns.incr/1, l) # => [2, 3, 4, 5, 6]
Hhfuns.map(&Hhfuns.decr/1, l) # => [0, 1, 2, 3, 4]
6.2 無名関数
defmodule Hhfuns do
def increment([]), do: []
def increment([h|t]), do: [h+1|increment(t)]
def decrement([]), do: []
def decrement([h|t]), do: [h-1|decrement(t)]
def map(_, []), do: []
def map(f, [h|t]), do: [f.(h)|map(f,t)]
def incr(x), do: x + 1
def decr(x), do: x - 1
end
f = fn() -> :a end
f.() # => :a
l = [1,2,3,4,5]
Hhfuns.map(fn(x) -> x + 1 end, l) # => [2, 3, 4, 5, 6]
Hhfuns.map(fn(x) -> x - 1 end, l) # => [0, 1, 2, 3, 4]
# Elixir ではこんな風にも書ける
Hhfuns.map(&(&1 + 1), l) # => [2, 3, 4, 5, 6]
Hhfuns.map(&(&1 - 1), l) # => [0, 1, 2, 3, 4]
Elixir での無名関数は
f = fn
(0) -> "ぜろ"
(1) -> "いち"
(_) -> "そのた"
end
f.(0) # => "ぜろ"
f.(1) # => "いち"
f.(2) # => "そのた"
となる.(今調べるまで Elixir でも無名関数のパターンマッチができることを知らなかった!)
また無名関数を &(...)
とも書ける.
この場合無名関数の中では引数 1 は &1
,引数 2 は &2
のように表されている.
prepare_alarm = fn(room) ->
IO.puts("Alarm set in #{room}")
fn() ->
IO.puts("Alarm tripped in #{room}! Call Batman!")
end
end
alarm_ready = prepare_alarm.("bathroom") # => Alarm set in bathroom
alarm_ready.() # => Alarm tripped in bathroom! Call Batman!
関数のスコープとクロージャは同じ
defmodule Hhfuns do
def a do
secret = "pony"
fn -> secret end
end
def b(f) do
"a/0's password is " <> f.()
end
end
Hhfuns.b(Hhfuns.a) # => a/0's password is pony
defmodule Hhfuns do
def map(_, []), do: []
def map(f, [h|t]), do: [f.(h)|map(f,t)]
end
:math.pow(5, 2) # => 25.0
base = 2
power_of_two = fn(x) -> :math.pow(base, x) end
Hhfuns.map(power_of_two, [1,2,3,4]) # => [2.0, 4.0, 8.0, 16.0]
Elixir でのスコープの再定義はこんな感じ.
Elixir の場合は ^
がないと再束縛できる(できてしまう)のでエラーにならないことに注意すること.
fn ->
a = 1
fn -> ^a = 2 end
end.().()
#> orgmode_elixir_src.exs:6: warning: the result of the expression is ignored (suppress the warning by assigning the expression to the _ variable)
#> orgmode_elixir_src.exs:14: warning: no clause will ever match
#> ** (MatchError) no match of right hand side value: 2
#> orgmode_elixir_src.exs:14: anonymous fn/0 in :elixir_compiler_0.__FILE__/1
#> (elixir) lib/code.ex:307: Code.require_file/2
fn ->
a = 1
fn(a) -> ^a = 2 end
end.().(2) # => 2
6.3 map、filter、foldなど
defmodule Hhfuns do
# 偶数だけを保持する
def even(l), do: Enum.reverse(even(l, []))
defp even([], acc), do: acc
defp even([h|t], acc) when rem(h, 2) === 0, do: even(t, [h|acc])
defp even([_|t], acc), do: even(t, acc)
# 60歳以上の男性だけ保持する
def old_men(l), do: Enum.reverse(old_men(l, []))
defp old_men([], acc), do: acc
defp old_men([person = {:male, age} | people], acc) when age > 60 do
old_men(people, [person|acc])
end
defp old_men([_|people], acc), do: old_men(people, acc)
def filter(pred, l), do: Enum.reverse(filter(pred, l, []))
defp filter(_, [], acc), do: acc
defp filter(pred, [h|t], acc) do
case pred.(h) do
true -> filter(pred, t, [h|acc])
false -> filter(pred, t, acc)
end
end
end
numbers = Enum.to_list(1..10)
Hhfuns.filter(&(rem(&1, 2) === 0), numbers) # => [2, 4, 6, 8, 10]
Hhfuns.even(numbers) # => [2, 4, 6, 8, 10]
people = [{:male, 45}, {:female, 67}, {:male, 66}, {:female, 12}, {:unknown, 174}, {:male, 74}]
Hhfuns.filter(fn ({gender, age}) -> gender === :male && age > 60 end, people) # => [{:male, 66}, {:male, 74}]
Hhfuns.old_men(people) # => [{:male, 66}, {:male, 74}]
pred は predicate (述語) の略.
ちなみに Lisp で evenp
みたいに最後が p
になっているのもこれと同じ意味で,
Ruby での ?
マークで終わるメソッドと同じような慣例だ.
Fold について.
defmodule Hhfuns do
# リストの最大値を見つける
def max([h|t]), do: max2(t, h)
defp max2([], max), do: max
defp max2([h|t], max) when h > max, do: max2(t, h)
defp max2([_|t], max), do: max2(t, max)
# リストの最小値を見つける
def min([h|t]), do: min2(t, h)
defp min2([], min), do: min
defp min2([h|t], min) when h < min, do: min2(t, h)
defp min2([_|t], min), do: min2(t, min)
# リストの全要素の合計を出す
def sum(l), do: sum(l, 0)
defp sum([], s), do: s
defp sum([h|t], s), do: sum(t, h + s)
def fold(_, start, []), do: start
def fold(f, start, [h|t]), do: fold(f, f.(h, start), t)
end
list = [1,7,3,5,9,0,2,3]
[h|t] = list
Hhfuns.fold(fn(a, b) when a > b -> a
(_, b) -> b
end,
h, t) # => 9
Hhfuns.max(list) # => 9
Hhfuns.fold(fn(a, b) when a < b -> a
(_, b) -> b
end,
h, t) # => 0
Hhfuns.min(list) # => 0
Hhfuns.fold(fn(a, b) -> a + b end, 0, Enum.to_list(1..6)) # => 21
Hhfuns.sum(Enum.to_list(1..6)) # => 21
引数の順番が Elixir の慣例とは異なっていることに気づいたが,実装したあとだったのでそのまま書いておく.
def fold([h|t], start, f)
が Elixir Way だと思う.第一引数に subject,最後に function がくるはず.
Elixir で fold に相当する Enum.reduce/3 の引数をみたら,予想通りだった.うむ.