技術メモ

プログラミングとか電子工作とか

Elixir入門(第八章 モジュールと関数 その2)

f:id:ysmn_deus:20190122112104p:plain

どうも、靖宗です。
前回関数を途中でぶった切ってしまったので続き。

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の命名規則で意味をなさない場合に利用するそうです。
この場合はbnilだった場合にsepがたとえどんな値でも結果に影響を与えない、という意味合いを引数名に出してるということでしょう。

hexdocs.pm

デフォルト値を使う場合は関数のオーバーラップにご用心とのこと。
アリティが違う関数を宣言してても、挙動としてオーバーラップになってしまう場合もあり得ます。

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"

となるみたいです。デフォルト値の意味ないからやめようね!みたいな意味合いでしょうか。
確かに上のコードはデフォルト値を書いてる意味がほとんど無いです。

まだまだ、道のりは長い・・・