fukuoka.ex代表のpiacereです
今回もご覧いただいて、ありがとうございます
他言語では、「if」は「言語の構文」として実装されるのに対し、Elixirは「関数」として実装されています
それを実感するコラムを書いてみました
Elixirのifがタダの関数であることを確認する
iexを起動して、普通のifの動きを確認してください
iex> if "abc" == "abc", do: "abc is abc"
"abc is abc"
iex> if "abc" == "xyz", do: "abc is xyz"
nil
do句を無くすと、以下のエラーメッセージが返ってきます
iex> if "abc" == "abc"
** (CompileError) iex: undefined function if/1
「undefined function if/1」、つまり、ifという関数は、1つの引数を取るバージョンが無い、ということを意味しています
つまり、こんな関数呼出をしたのと同じだった、ということです
iex> if( "abc" == "abc" )
** (CompileError) iex: undefined function if/1
では、このメッセージを信じて、2つの引数を渡してみると…
iex> if( "abc" == "abc", "hoge" )
** (ArgumentError) invalid or duplicate keys for if, only "do" and an optional "else" are permitted
(elixir) lib/kernel.ex:2920: Kernel.build_if/2
(elixir) expanding macro: Kernel.if/2
iex:28: (file)
どうやら、キーとして、「do」もしくは「else」以外は、許可されていないことが分かります
であれば、doをキーとした処理を渡してみましょう
iex> if( "abc" == "abc", do: "abc is abc" )
"abc is abc"
iex> if( "abc" == "xyz", do: "abc is xyz" )
nil
冒頭のifと同じことができるようになりました
上記ifを関数プロトタイプとして書くと、こんな感じで、まんま関数なことが分かりました
if( condition, do: true_execution )
アリティを指定して確認してみる
前々回のコラムでやった「アリティ」を使って、引数の個数を指定して、直接、調べてみましょう
iex> &if/1
** (UndefinedFunctionError) function :erl_eval.if/1 is undefined or private
iex> &if/2
** (ArgumentError) invalid or duplicate keys for if, only "do" and an optional "else" are permitted
(elixir) lib/kernel.ex:2920: Kernel.build_if/2
(elixir) expanding macro: Kernel.if/2
iex:17: (file)
上述したものと同じエラーが返ってくることが確認できました
関数として、if/1は存在せず、if/2は存在しています
elseはどう実装されているか?
次に、elseについても調べてみましょう
iex> if( "abc" == "abc", do: "abc is abc", else: "abc is not abc" )
"abc is abc"
iex> if( "abc" == "xyz", do: "abc is abc", else: "abc is not abc" )
"abc is not abc"
さて、この「do: ~」と「else: ~」を見ると、3つの引数を取るifが、存在しているかのように見えます
関数プロトタイプとして書くと、こんな感じでしょうか
if( condition, do: true_execution, else: false_execution )
では、アリティで確認してみましょう
iex> &if/3
** (UndefinedFunctionError) function :erl_eval.if/3 is undefined or private
おや?引数を3つ取るifは、存在していないようです…
では、どうやって、elseを実装しているのでしょう?
ここで、関数プロトタイプを見てみると、Elixirで許可されていない構文が使われていることに気付きます
以下をiexで実行してみましょう
iex> do: "hoge"
** (SyntaxError) iex: syntax error before: do
iex> hoge: "hoge"
** (SyntaxError) iex: syntax error before: hoge
このタイプの記述が許されるのは、キーワードリスト内か、マップ内に限定されます
iex> [ do: "hoge" ]
[do: "hoge"]
iex> %{ do: "hoge" }
%{do: "hoge"}
ifの第2引数に、キーワードリストと、マップを渡してみて、確認しましょう
iex> if( "abc" == "abc", [ do: "abc is abc" ] )
"abc is abc"
iex> if( "abc" == "abc", %{ do: "abc is abc" } )
** (ArgumentError) invalid or duplicate keys for if, only "do" and an optional "else" are permitted
(elixir) lib/kernel.ex:2920: Kernel.build_if/2
(elixir) expanding macro: Kernel.if/2
iex:40: (file)
どうやら、キーワードリストが正解のようです
ifの「do:」と「else:」はキーワードリスト
「do:」がキーワードリストであることが確認できたので、「else:」もキーワードリストに並べてみましょう
iex> if( "abc" == "abc", [ do: "abc is abc", else: "abc is not abc" ] )
"abc is abc"
iex> if( "abc" == "xyz", [ do: "abc is abc", else: "abc is not abc" ] )
"abc is not abc"
うまく動きましたー
これを踏まえて、関数プロトタイプを書くと、2つの引数を取る関数で、第2引数は「do:」と「else:」がキーとして許可されたキーワードリストとなります
if( condition, [ do: true_execution, else: false_execution ] )
ifをiex内ヘルプで確認してみる
最後にタネ明かしですが、iex内で、「h ~」とすると、ifの正体が確認できます
iex> h if
* defmacro if(condition, clauses)
Provides an `if/2` macro.
This macro expects the first argument to be a condition and the second
argument to be a keyword list.
…
if/2は、defmacroで定義されており、第2引数をキーワードリストとして扱っていることが明かされています
ifもKernelモジュールの関数
if/2は、前々回のコラムで扱った「+」と同様、Kernelモジュールの関数(マクロ)として定義されていることが、以下リファレンスで確認できます
https://hexdocs.pm/elixir/Kernel.html#if/2
iex内ヘルプでも、Kernel無のifと同一であることが確認できます
iex> h Kernel.if
* defmacro if(condition, clauses)
Provides an `if/2` macro.
This macro expects the first argument to be a condition and the second
argument to be a keyword list.
…