Elixirのforもタダの関数（だが、ただならぬ関数）

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

[ifが関数であること](https://qiita.com/piacere_ex/items/c7c31499a6bde7f1fe0e)を調べましたが、今度は、forが関数であることを調べてみましょう

# Elixirのforが関数であることを確認する

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

```elixir
iex> for x <- [ 1, 3, 5 ], do: x + 2
[3, 5, 7]
```

まずforが、リストを返すものであることを、ここで初めて知る方もいたのでは無いでしょうか？

つまり、本質的には、Enum.mapと変わらない、ということです

逆に、他言語でのforとは、だいぶノリが異なることも、ここから分かります

では、ifのときと同じく、「do:」以降を消してみましょう（ちなみに、ここからの流れは、[ifのときの調査内容](https://qiita.com/piacere_ex/items/c7c31499a6bde7f1fe0e)を最後から辿ったら、省略できる訳ですが、せっかくなので、イチイチ調べてみます）

```elixir
iex> for x <- [ 1, 3, 5 ]
** (CompileError) iex: missing :do option in "for"
```

ifのときは、「undefined function」と出てきましたが、直接、「do:」が無い、というエラーメッセージが返ってきました

まぁ、ひとまず、ifと同じく、カッコで囲んで実行したり、「do:」以外の第2引数を渡したら、エラーメッセージは同じでした

```elixir
iex> for( x <- [ 1, 3, 5 ] )
** (CompileError) iex: missing :do option in "for"
iex> for( x <- [ 1, 3, 5 ], "hoge" )
** (CompileError) iex: missing :do option in "for"
```

「do:」を渡してみましょう

```elixir
iex> for( x <- [ 1, 3, 5 ], do: x + 2 )
[3, 5, 7]
```

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

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

forもアリティを使って、引数の個数を指定して、直接、調べてみましょう

```elixir
iex> &for/1
** (CompileError) iex: missing :do option in "for"
    (elixir) src/elixir_fn.erl:14: anonymous fn/4 in :elixir_fn.expand/3
    (stdlib) lists.erl:1354: :lists.mapfoldl/3
    (elixir) src/elixir_fn.erl:19: :elixir_fn.expand/3
iex> &for/2
** (CompileError) iex: missing :do option in "for"
    (elixir) src/elixir_fn.erl:14: anonymous fn/4 in :elixir_fn.expand/3
    (stdlib) lists.erl:1354: :lists.mapfoldl/3
    (elixir) src/elixir_fn.erl:19: :elixir_fn.expand/3
iex> &for/3
** (CompileError) iex: missing :do option in "for"
    (elixir) src/elixir_fn.erl:14: anonymous fn/4 in :elixir_fn.expand/3
    (stdlib) lists.erl:1354: :lists.mapfoldl/3
    (elixir) src/elixir_fn.erl:19: :elixir_fn.expand/3
```

ifと異なり、引数が2つでは無いし、3つになっても、同じエラーメッセージでした…

では、0個だと、どうでしょう？

```elixir
iex> &for/0
** (CompileError) iex:54: undefined function for/0
```

0個は、存在しないようです

それでは、4個だと、どうでしょう？

```elixir
iex> &for/4
** (CompileError) iex: missing :do option in "for"
    (elixir) src/elixir_fn.erl:14: anonymous fn/4 in :elixir_fn.expand/3
    (stdlib) lists.erl:1354: :lists.mapfoldl/3
    (elixir) src/elixir_fn.erl:19: :elixir_fn.expand/3
```

なるほど、どうやらforは、可変長の引数であるようです

# forの仕様を知っていると…

実は、Elixirのforの仕様を知っていると、上記は調べなくても、類推できたのです

forは、たとえば、以下のような記述ができます

```elixir
iex> for x <- [ 1, 3, 5 ], y <- [ 2, 4, 6 ], do: x + y
[3, 5, 7, 5, 7, 9, 7, 9, 11]
```

こんな感じで、2つ以上のリスト分解の組み合わせをループできるんですねー

更にforは、こんな条件判定も書けます

```elixir
iex> for x <- [ 1, 3, 5 ], x < 4, do: x
[1, 3]
```

複数リスト分解と、複数条件判定を組み合わせると、こんな書き方もできます

```elixir
iex> for x <- [ 1, 3, 5 ], y <- [ 2, 4, 6 ], x < 4, y < 5, do: x + y
[3, 5, 5, 7]
```

リスト分解と、条件判定は、特に順番を意識せずにも書けるので、こんな書き方だって、できます

```elixir
for x <- [ 1, 3, 5 ], x < 4, y <- [ 2, 4, 6 ], y < 5, do: x + y
[3, 5, 5, 7]
```

つまり、「do:」が最後にあれば、幾つでもリストを並べられるし、条件判定も複数書けるのが、Elixirのforなのです

ここで、「do:」は、if同様、単独で成り立つ構文では無く、暗黙のキーワードリストであることが推測できます

```elixir
iex> for( x <- [ 1, 3, 5 ], [ do: x + 2 ] )
[3, 5, 7]
```

実行できました

ダメ元でマップを試すと、やはりダメでした

```elixir
iex> for( x <- [ 1, 3, 5 ], %{ do: x + 2 } )
** (CompileError) iex: missing :do option in "for"
```

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

最後にタネ明かしですが、iex内で、「h ～」とすると、forの正体が確認できます（[ifの経緯](https://qiita.com/piacere_ex/items/c7c31499a6bde7f1fe0e)を知っていれば、ここから始めればOKです）

```elixir
iex> h for
* defmacro for(args)

Comprehensions allow you to quickly build a data structure from
an enumerable or a bitstring.
　…
```

forは、defmacroで定義されており、引数が可変長であることが明かされています

# forはKernel.SpecialFormsモジュールの関数

for/nは、Kernelモジュールの関数では無く、Kernel.SpecialFormsモジュールの関数であることが、以下リファレンスで確認できます
https://hexdocs.pm/elixir/Kernel.SpecialForms.html#for/1

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

```elixir
iex> h Kernel.SpecialForms.for
* defmacro for(args)

Comprehensions allow you to quickly build a data structure from
an enumerable or a bitstring.
　…
```

# Kernel.SpecialFormsの他の関数を見てみると…

上記のリファレンスの左側を見ると、forだけで無い、Kernel.SpecialFormsモジュールの関数がリストされていますが、その中には、「 %{}」や「&」、「fn」、「quote」といった、およそ関数で実装されているようには見えない、特殊な構文が並んでいます

こういったところから、Elixirの内部実装の一端が、少しだけ垣間見えたのでは無いでしょうか？

さて最後に、モジュール名も分かったところで、Github上のElixir本体のソースコードを少しだけ覗いてみましょう
https://github.com/elixir-lang/elixir/blob/master/lib/elixir/lib/kernel/special_forms.ex

```elixir
defmodule Kernel.SpecialForms do
  @moduledoc """
  Special forms are the basic building blocks of Elixir, and therefore
  cannot be overridden by the developer.
  We define them in this module. Some of these forms are lexical (like
  `alias/2`, `case/2`, etc). The macros `{}/1` and `<<>>/1` are also special
  forms used to define tuple and binary data structures respectively.
  This module also documents macros that return information about Elixir's
  compilation environment, such as (`__ENV__/0`, `__MODULE__/0`, `__DIR__/0` and `__CALLER__/0`).
  Finally, it also documents two special forms, `__block__/1` and
  `__aliases__/1`, which are not intended to be called directly by the
  developer but they appear in quoted contents since they are essential
  in Elixir's constructs.
  """

  defmacrop error!(args) do
    quote do
      _ = unquote(args)

      message =
        "Elixir's special forms are expanded by the compiler and must not be invoked directly"

      :erlang.error(RuntimeError.exception(message))
    end
  end
　…
```

forの定義を見てみます

```elixir
1394:  defmacro for(args), do: error!([args])
```

ポイントは、unquoteが、幾つもの関数パターンマッチで構成されている点で、これはサクッと読みこなすことはできません

もし、いつか機会があれば、Elixirカーネルハックで再びお会いしましょう:wink:

# 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: