Elixirのifはタダの関数

[fukuoka.ex](https://fukuokaex.fun/)代表の[piacere](https://twitter.com/piacere_ex)です
今回もご覧いただいて、ありがとうございます:bow:

他言語では、「if」は「言語の構文」として実装されるのに対し、Elixirは「関数」として実装されています

それを実感するコラムを書いてみました

# Elixirのifがタダの関数であることを確認する

iexを起動して、普通のifの動きを確認してください

```elixir
iex> if "abc" == "abc", do: "abc is abc"
"abc is abc"
iex> if "abc" == "xyz", do: "abc is xyz"
nil
```

do句を無くすと、以下のエラーメッセージが返ってきます

```elixir
iex> if "abc" == "abc"
** (CompileError) iex: undefined function if/1
```

「undefined function if/1」、つまり、ifという関数は、1つの引数を取るバージョンが無い、ということを意味しています

つまり、こんな関数呼出をしたのと同じだった、ということです

```elixir
iex> if( "abc" == "abc" )
** (CompileError) iex: undefined function if/1
```

では、このメッセージを信じて、2つの引数を渡してみると…

```elixir
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をキーとした処理を渡してみましょう

```elixir
iex> if( "abc" == "abc", do: "abc is abc" )
"abc is abc"
iex> if( "abc" == "xyz", do: "abc is xyz" )
nil
```

冒頭のifと同じことができるようになりました

上記ifを関数プロトタイプとして書くと、こんな感じで、まんま関数なことが分かりました

```elixir
if( condition, do: true_execution )
```

# アリティを指定して確認してみる

[前々回のコラム](https://qiita.com/piacere_ex/items/4a737cc62b485e561fd1)でやった「アリティ」を使って、引数の個数を指定して、直接、調べてみましょう

```elixir
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についても調べてみましょう

```elixir
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が、存在しているかのように見えます

関数プロトタイプとして書くと、こんな感じでしょうか

```elixir
if( condition, do: true_execution, else: false_execution )
```

では、アリティで確認してみましょう

```elixir
iex> &if/3
** (UndefinedFunctionError) function :erl_eval.if/3 is undefined or private
```

おや？引数を3つ取るifは、存在していないようです…

では、どうやって、elseを実装しているのでしょう？

ここで、関数プロトタイプを見てみると、Elixirで許可されていない構文が使われていることに気付きます

以下をiexで実行してみましょう

```elixir
iex> do: "hoge"
** (SyntaxError) iex: syntax error before: do
iex> hoge: "hoge"
** (SyntaxError) iex: syntax error before: hoge
```

このタイプの記述が許されるのは、キーワードリスト内か、マップ内に限定されます

```elixir
iex> [ do: "hoge" ]
[do: "hoge"]
iex> %{ do: "hoge" }
%{do: "hoge"}
```

ifの第2引数に、キーワードリストと、マップを渡してみて、確認しましょう

```elixir
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:」もキーワードリストに並べてみましょう

```elixir
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:」がキーとして許可されたキーワードリストとなります

```elixir
if( condition, [ do: true_execution, else: false_execution ] )
```

# ifをiex内ヘルプで確認してみる

最後にタネ明かしですが、iex内で、「h ～」とすると、ifの正体が確認できます

```elixir
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は、[前々回のコラム](https://qiita.com/piacere_ex/items/4a737cc62b485e561fd1)で扱った「+」と同様、Kernelモジュールの関数（マクロ）として定義されていることが、以下リファレンスで確認できます
https://hexdocs.pm/elixir/Kernel.html#if/2

iex内ヘルプでも、Kernel無のifと同一であることが確認できます

```elixir
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.
　…
```

# p.s.「いいね」よろしくお願いします

よろしければ、ページ左上の ![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/4d515047-cc48-382e-c2b1-3ad0cc50dbbf.png) や ![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/a4e3da58-70a3-4197-95a2-6a6906650d01.png) のクリックをお願いしますー:bow: