技術メモ

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

Elixir入門(第五章 制御構文)

f:id:ysmn_deus:20190122112104p:plain

どうも、靖宗です。
日本語訳ないってことは翻訳のところにプルリク送った方がいいのかな、とも思ったんですがgemやらbundleやらでやる気が消失・・・

この章はcase, cond, and ifということでおそらく制御構文でしょう。

case

いわゆるSwitch構文でしょうか。

iex(1)> case [1, 2, 3] do
...(1)> [4, 5, 6] ->
...(1)> "This clause wont't match."
...(1)> [1, x, 3] ->
...(1)> "This clause will match and bind x to 2 in this clause."
...(1)> _ ->
...(1)> "This clause would match any value."
...(1)> end
warning: variable "x" is unused (if the variable is not meant to be used, prefix it with an underscore)
  iex:4

"This clause will match and bind x to 2 in this clause."

x使ってないので警告が出てますが概ねSwitchということでよさそうです。
パターンマッチングによって挙動を変えたい場合などに利用するのでしょうか。
地味に値も代入できるのは良いですね。

ピンオペレータによる再代入の回避

ここでピンオペレータが活躍するようです。

iex(3)> case 10 do
...(3)> x ->
...(3)> "match?"
...(3)> IO.puts x
...(3)> _ -> "Will match"
...(3)> end
warning: code block contains unused literal "match?" (remove the literal or assign it to _ to avoid warnings)
  iex

10
:ok
iex(4)>

やっぱり、ピンオペレータを使わないと再代入されちゃいます。

iex(5)> case 10 do
...(5)> ^x ->
...(5)> "Won't match"
...(5)> _ -> "Will match"
...(5)> end
"Will match"

ピンオペレータで再代入を回避できます。
ようやく意義を見出しました!

変数の条件(guard)

パターンマッチングで代入して使用する際に変数に条件を設けられるようです。

iex(6)> case [1, 2, 3] do
...(6)> [1, x, 3] when x > 0 ->
...(6)> "Will match"
...(6)> _ ->
...(6)> "Would match, if guard condition were not satisfied"
...(6)> end
"Will match"
iex(7)> case [1, 2, 3] do
...(7)> [1, x, 3] when x < 0 -> # ためしに0未満にしてみる
...(7)> "Will match"
...(7)> _ ->
...(7)> "Would match, if guard condition were not satisfied"
...(7)> end
"Would match, if guard condition were not satisfied"

なるほどね。
ちなみに、この条件の事を「guard」と呼ぶようです。

guard句のエラーを利用する

ベストプラクティスなのか分かりませんが、guard句にエラーが出るような書き方を利用することもありそうです。

iex(9)> case 1 do
...(9)> x when hd(x) -> "Won't match"
...(9)> x -> "Got #{x}"
...(9)> end
"Got 1"

hd(x)はリストxの先頭を抽出する関数ですので、xがリストじゃないとエラーが起きます。
これを利用してxがリストの時と数値の時で挙動を変えれる様です。

あと、地味に文字列中に#{変数名}という記法が出てきてます。多分文字列中に変数を代入する記法だと思います。(Javascriptでいう${変数}的な?)

guardに関してはドキュメントを参照するように!と書いてあるので、そのうち読みたい。

hexdocs.pm

どこにもマッチしないと怒るぞ!

case doの後に何個か条件を書いていきますが、そのどれともマッチしないと怒られるようです。
嫌ならたぶん _ -> を無難に書いておけばいいんじゃないでしょうか。

無名関数はcaseみたいに書ける

iex(10)> f = fn
...(10)> x, y when x > 0 -> x + y
...(10)> x, y -> x * y
...(10)> end
#Function<12.128620087/2 in :erl_eval.expr/5>
iex(11)> f.(1, 3)
4
iex(12)> f.(-1, 3)
-3

ifとか書くのめんどくさいときに、とかかな。
ただ、この条件分岐は引数の数(アリティ)が同じじゃないとエラーが出る。
おそらくElixirは関数を関数名/アリティで一意に定義できるようになってる仕様上のことだと思う。一貫性があって素敵。

cond

なんやこれ
多分conditionのcond。

iex(15)> cond do
...(15)> 2 + 2 == 5 -> "This is never true"
...(15)> 2 * 2 == 3 -> "Nor this"
...(15)> true -> "This is always true"
...(15)> end
"This is always true"

if ... else if ... else if ... elseと同等らしい。
じゃあcaseはマッチしたあとも捜索するのか?という疑問が湧いたので一応チェック。

iex(13)> case [1, 2, 3] do
...(13)> [1, 2, x] -> "Match #{x}"
...(13)> [1, y, 3] -> "Match #{y}"
...(13)> end
"Match 3"

一番最初にマッチした箇所で終了しているので、breakみたいなのは要らないみたいです。

何か引数を取ってパターンマッチングで条件分岐 => case
引数は要らないけど条件分岐 => cond
といった感じでしょうか。

なお、condは比較演算子のあたりと同様にnilとfalse以外はtrueと判断するようです。

if と unless

他の言語と同様にifが使えます。unlessはあったりなかったりだと思いますが、ifの逆(if not)という認識で良さそうです。

iex(16)> if true do
...(16)> "This works"
...(16)> end
"This works"

if句で条件に入らないとnilが返ってきます。

iex(17)> if false do
...(17)> "This works"
...(17)> end
nil

else文も取れます。

iex(1)> if nil do
...(1)> "This won't be seen"
...(1)> else
...(1)>  "This will"
...(1)> end
"This will"

ifやunlessは基礎文法として実装されているというよりはマクロとして実装されている、という記述がありましたが、そもそもこの言語でマクロってどういう物を指すのか不明なので読み飛ばしちゃいます。(おそらく関数みたいな物だとは予想。)

do/end ブロック

上記までの制御構文でdo~~~endと書いてきたがこんな書き方もあるよ!

iex(2)> if true, do: 1 + 2
3

打つ文字数が少なくて済むので、ちょっとした条件分岐などはこういう書き方でも良いかもしれません。

iex(3)> if false, do: :this, else: :that
:that

else句もとれます。

do/endブロックは下記のようにも書き換えることができます。

iex> if true do
...>   a = 1 + 2
...>   a + 10
...> end
13
iex> if true, do: (
...>   a = 1 + 2
...>   a + 10
...> )
13

条件分岐の中に複数の処理がある場合はdo/endブロックを利用した方がよさそうです。
do/endブロックは必ず最も外側のスコープになるので

iex> is_number if true do
...>  1 + 2
...> end

iex> is_number(if true) do
...>  1 + 2
...> end

と解釈されます。(is_number/2は存在しないのでエラーになります。)
もしif true do~endをis_numberしたい場合は括弧を利用して

iex> is_number(if true do
...>  1 + 2
...> end)

と書きましょう。とのことです。do/endブロックのスコープを意識しましょうという所でしょうか。
意外と凡ミスに繋がりそうなので気をつけよう。