どうも、靖宗です。
前回関数を途中でぶった切ってしまったので続き。
Function capturing(関数キャプチャ?)
たぶん「モジュール内の関数を変数にぶち込んで無名関数として使えるよ!」って項目。
わざわざそうするメリットがあるんだろうか?
iex> Math.zero?(0) true iex> fun = &Math.zero?/1 &Math.zero?/1 iex> is_function(fun) true iex> fun.(0) true
モジュール内の関数を捉えて無名関数として変数に格納する、的な意味合いからキャプチャと言ってるのかな?
&
がC言語のポインタっぽい扱い。関数は今まで説明に使ってきた関数名/アリティ
の記法でかくみたいです。
若干無名関数とモジュール内にある関数で分かりにくいかもしれませんが、関数名と括弧の間に.
があるかないかで見分けられる。
この関数キャプチャはローカル関数やインポートした関数でも使用できる。
iex(5)> fun = &is_function/1 &:erlang.is_function/1 iex(7)> fun.(fun) true
このキャプチャ記法は関数を作るときのショートカットにも使える。
iex> fun = &(&1 + 1) #Function<6.71889879/1 in :erl_eval.expr/5> iex> fun.(1) 2
('ω')。o(????????????)
と思ったが冷静に説明文を読むと$1
は関数の1番目の引数となるそうです。
つまり$x
はx番目の引数となると。なので&(&1 + 1)
は
fn x -> x + 1 end
と同義になります。fn $1 -> $1 + 1 end
のイメージ?
それを踏まえてもう一個例
iex> fun2 = &"Good #{&1}" #Function<6.127694169/1 in :erl_eval.expr/5> iex)> fun2.("morning") "Good morning"
なるほど。簡単な無名関数ならこう書いた方が早そうですね。
なんとなく言及されてませんでしたが、変数を明示して変数の個数が確かなので、アリティの表記はいらなくなってるみたいです。
なので、モジュールをインポートするときも
iex> fun = &List.flatten(&1, &2) &List.flatten/2 iex> fun.([1, [[2], 3]], [4, 5]) [1, 2, 3, 4, 5]
といった感じにアリティを書かず変数を書いてキャプチャすることができるそうです。アリティ表記の方が早そう。
Default arguments(デフォルト引数)
Elixirでもデフォルトの引数を設定できるみたいです。
自分はPythonをよく使うので、いわゆる
def hoge(arg1="default"): pass
の"default"
にあたるところでしょうか。
defmodule Concat do def join(a, b, sep \\ " ") do a <> sep <> b end end IO.puts Concat.join("Hello", "world") #=> Hello world IO.puts Concat.join("Hello", "world", "_") #=> Hello_world
モジュール内関数の宣言で変数名 \\ デフォルト値
の様な書き方ですね。
デフォルト値は基本的にどんな値でも取れるそうです。
guard句などを利用した複数の節にも使えるみたいです。習うより慣れましょう
defmodule Concat do def join(a, b \\ nil, sep \\ " ") def join(a, b, _sep) when is_nil(b) do a end def join(a, b, sep) do a <> sep <> b end end IO.puts Concat.join("Hello", "world") #=> Hello world IO.puts Concat.join("Hello", "world", "_") #=> Hello_world IO.puts Concat.join("Hello") #=> Hello
Concat.join/3
が2個宣言されてますが、その共通のデフォルト値として一個目の関数が宣言されてます。
また、2個目の関数で引数の一部が_sep
になってますが、このアンダースコアを変数名に利用するのはElixirの命名規則で意味をなさない場合に利用するそうです。
この場合はb
がnilだった場合にsepがたとえどんな値でも結果に影響を与えない、という意味合いを引数名に出してるということでしょう。
デフォルト値を使う場合は関数のオーバーラップにご用心とのこと。
アリティが違う関数を宣言してても、挙動としてオーバーラップになってしまう場合もあり得ます。
defmodule Concat do def join(a, b) do IO.puts "***First join" a <> b end def join(a, b, sep \\ " ") do IO.puts "***Second join" a <> sep <> b end end
一応コンパイルするときは警告してくれるそうですが、通るみたいですね。
上記のモジュールをインポートしてるとき、
iex> Concat.join "Hello", "world" ***First join "Helloworld" iex> Concat.join "Hello", "world", "_" ***Second join "Hello_world"
となるみたいです。デフォルト値の意味ないからやめようね!みたいな意味合いでしょうか。
確かに上のコードはデフォルト値を書いてる意味がほとんど無いです。
まだまだ、道のりは長い・・・