Enum.map/2を分解する

# `Enum.map/2`のことを考えない日などあるだろうか？

ない。Elixirを書いていれば人はmapせずにはいられない。

そんなわけで`Enum.map/2`の中身を知るべく、分解してみる。

記事中**太字**で書いている部分は個人的に注意している一般的なElixir Tipsでもある。

# [enum.ex](https://github.com/elixir-lang/elixir/blob/v1.3.4/lib/elixir/lib/enum.ex#L1181)

## `def map(enumerable, fun)`

```ex
defmodule Enum do
  # ...

  @type t :: Enumerable.t
  @type element :: any

  require Stream.Reducers, as: R

  # ...

  @spec map(t, (element -> any)) :: list
  def map(enumerable, fun)

  def map(enumerable, fun) when is_list(enumerable) do
    :lists.map(fun, enumerable)
  end

  def map(enumerable, fun) do
    reduce(enumerable, [], R.map(fun)) |> :lists.reverse
  end

  # ...
end
```

まず注目すべきは、渡された`enumerable`が`list`だったら、`:lists`にぶん投げるところだ。**Erlangに存在しているものを再生産しないようにする**のは、Elixir開発時に気をつける必要がある。

ここで扱いたいのは`list`だけではなく、`Enumerable.t`つまり数え上げる事に関するprotocolを実装した任意のdata typeであるから、より抽象的な手続きも用意せねばならない。

しかし第2のclauseも最終的に`:lists.reverse`しているところを見ると、どうやら結局は`list`に落ちてくるようである。これはElixirで開発していればお馴染みのことだろう。`Enum.map/2`の出力は常に`list`である。[^1]

[^1]: 例えば`map`を入れたのに`list`が返ってきて違和感、といった話を時々聞く。[`Collectable`](http://elixir-lang.org/docs/stable/elixir/Collectable.html#content)に意図の説明があり、端的にはそのギャップは`Enum.into/3`で埋めよ、とされる。

## `reduce/3`

第2clauseはすぐさま`Enum.reduce/3`している。表現力の高い(、言い換えればだいたいなんでもできる)`reduce/3`は様々な関数のビルドブロックになる。同じ理由で`for/1`comprehensionもビルドブロックとして多用される。

**逆に言うと、`reduce/3`や`for/1`は表現力が高すぎる**ので、言語使用者側がそれだけを使ってコードを書いてしまうと、それぞれのchunkが部分的に何をしているものなのか理解しづらくなる。[^2]
だからこそ`Enum`には多数の関数が標準で用意されている。結局中では`reduce/3`や`for/1`に落ちると知りつつも、**文脈に応じて呼び出す関数を変えることで、意図を読み手に伝えやすくできる**。

[^2]: もちろん、accumulatorを使ってある程度複雑な処理をしながらも、1回のループで走査したいと思ったら`reduce/3`に頼る必要はでてくる。適切な名前の関数で包んでやれば良い。また、対象のデータサイズがある程度少なく、増加しないことがわかっているようなケースでは、`Enum`に用意されている関数を組み合わせて書き、見た目のわかりやすさを優先してもいいかもしれない。

そして何やら`Stream.Reducers.map/1`なるmacroが登場している。

# [stream/reducers.ex](https://github.com/elixir-lang/elixir/blob/v1.3.4/lib/elixir/lib/stream/reducers.ex#L115)

## `Stream.Reducers.map/1`

```ex
defmodule Stream.Reducers do
  # Collection of reducers shared by Enum and Stream.

  # ...

  defmacro map(callback, f \\ nil) do
    quote do
      fn(entry, acc) ->
        next(unquote(f), unquote(callback).(entry), acc)
      end
    end
  end

  # ...
end
```

つまり`reduce/3`をビルドブロックとして作られる関数定義の際、頻出となる無名関数をまとめているだけの話だ。

`Enum.map/2`の定義内に手動で展開してやると、こう変わる。

```ex
  def map(enumerable, fun) do
    reduce(enumerable, [], fn(entry, acc) ->
      next(nil, fun.(entry), acc)
    end)
    |> :lists.reverse
  end
```

Mapper関数の適用が行われたが、同時に`next/3`なる物が出てきた。prefix無しで呼び出せることが期待されているということは、同じ`Enum`module内にあるはずだ。

# 再びenum.ex

## `next/3`

```ex
defmodule Enum do
  # ...

  defmacrop next(_, entry, acc) do
    quote do: [unquote(entry) | unquote(acc)]
  end

  # ...
end
```

ごく単純な代物である。`acc`が`list`であることを暗に要求しつつ、`entry`と`acc`から次(next)の`acc`として使う`list`を生成している。
Module privateなmacroなので、これくらいの前提は暗黙に置いても読み手の心の静謐を犯すことはないというのだろう。

**Elixirのmacroには`@doc`は書けても`@spec`は書けなかったり**、もっと一般的には関数やmacroにどの程度細かくdocやannotationを付すか迷ったり、といったことで我々は往々にして立ち止まる。命名、可視性、責任範囲、処理内容、平均的な読み手のレベルなどからじっと考えれば、自ずと要・不要は明らかになるという例として大げさに受け取ってみる。

再度`Enum.map/2`に取り込んでやると、こう展開される。

```ex
  def map(enumerable, fun) do
    reduce(enumerable, [], fn(entry, acc) ->
      [fun.(entry) | acc]
    end)
    |> :lists.reverse
  end
```

かんぺきにわかりました。

# 結び

こうなると`Enum.reduce/3`を分解してみたくなる。別でやるかも。

**`Enum`に用意されている関数を文脈によって適切に使い分ける**ことはかなり重要と感じている。