[Elixir]匿名関数をパターンマッチする


この記事は fukuoka.ex Elixir／Phoenix Advent Calendar 2020 の5日目です． 
4日目は，@takasehideki 先生の「[ElixirでIoT#4.1.2：Docker(とVS Code)だけ！でNerves開発環境を整備する](https://qiita.com/takasehideki/items/27005ba9c0d9eb693ea9)」でした!

---

**動作環境**

- macOS Mojave(10.14.6)
- Erlang/OTP 22 [erts-10.5] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [hipe] [sharing-preserving]
- Elixir 1.10.4 (compiled with Erlang/OTP 21)

---


# 必要知識
## 匿名関数
ElixirではEnum.mapやReduceに関数を渡すことでデータを加工することができます．
標準で提供されているモジュールの関数を処理として渡すというのが主な使い時です．

下記にIExで定義，呼び出しするコードを載せます．

```Elixir
iex> func = fn x -> x + 1 end
#Function<7.126501267/1 in :erl_eval.expr/5>
iex> func.(3)
4
```

では，上記の変数`func`が何者か知りたいので`i`をつけて確認します．

```Elixir
iex> i func
Term
  #Function<7.126501267/1 in :erl_eval.expr/5>
Data type
  Function
Type
  local
Arity
  1
Description
  This is an anonymous function.
Implemented protocols
  Enumerable, IEx.Info, Inspect
```

## 匿名関数を引数にする

匿名関数を引数にすれば，渡した先で関数が実行できます．

```Elixir
iex> func_exec = fn data, func -> func.(data) end
#Function<13.126501267/2 in :erl_eval.expr/5>
iex> func_exec.(1, fn x -> x + 1 end)
2
```

## 関数の仮引数でパターンマッチ

匿名関数の仮引数でもパターンマッチができます．

```Elixir
iex> func_ptn = fn {x, y} -> x + y end
#Function<7.126501267/1 in :erl_eval.expr/5>
iex> func_ptn.({1, 2})
3
```

# 匿名関数をパターンマッチ？

基本を押さえたところで本題です．見やすさのために名前付き関数で説明します．
今回やることのイメージは，

任意の匿名関数を関数のパターンマッチで分けて処理を変えることです．
下記は，値xの2乗が来たとき，計算を`:math.pow/2`で行うように書き換えるコードのイメージです.


```Elixir:sample.exs
# ※イメージです
defmodule Sample do
  def replace_to_pow(fn x -> x * x end) do
    :math.pow(x, 2)
  end
end
```

こんなヘンテコな文法は通りませんので，メタプログラミングでなんとかしましょう．

```
$ elixir sample.exs
invalid pattern in match, fn is not allowed in matches
```


## 匿名関数のAST

匿名関数を`quote`で包んでおくと，コンパイラに渡される中間表現を取得できます．
今後の説明では，コードの中間表現を`AST`と表記します．

後の手順でコピペできるように一工夫します．

```Elixir
iex> ( quote do: fn x -> x * x end ) |> Macro.escape() |> Macro.to_string() |> IO.puts
{:fn, [], [{:->, [], [[{:x, [], Elixir}], {:*, [context: Elixir, import: Kernel], [{:x, [], Elixir}, {:x, [], Elixir}]}]}]}
```

## AST |> 仮引数
では`replace`関数の引数に上記の結果をコピペします．

```Elixir:sample.exs
defmodule Sample do
  def replace_to_pow({:fn, [], [{:->, [], [[{:x, [], Elixir}], {:*, [context: Elixir, import: Kernel], [{:x, [], Elixir}, {:x, [], Elixir}]}]}]}) do
    IO.puts("MATCH!!")
  end
end

(quote do: fn x -> x * x end) 
|> Sample.replace_to_pow() 
|> Macro.to_string()
|> IO.puts()

```

コンパイルが通るか確認しましょう．

```bash
$ elixir sample.exs
MATCH!!
:ok
```

# 匿名関数をパターンマッチ！

では，最後に出力を整えておしまいです．
本当は仮引数内部の変数`x`をちゃんと解析しないといけないのですが，難易度が跳ね上がるので本記事では取り扱いません．

```Elixir:sample.exs
defmodule Sample do
  def replace_to_pow({:fn, [], [{:->, [], [[{:x, [], Elixir}], {:*, [context: Elixir, import: Kernel], [{:x, [], Elixir}, {:x, [], Elixir}]}]}]}) do
    quote do: fn x -> :math.pow(x, 2) end
  end
end

(quote do: fn x -> x * x end) 
|> Sample.replace_to_pow() 
|> Macro.to_string()
|> IO.puts()
```

実行結果です．

```bash
$ elixir escape.exs
BEFORE
fn x -> x * x end
AFTER
fn x -> :math.pow(x, 2) end

```

# まとめ
実はASTを仮引数にしてもコンパイルは通る．
AST・コードの中間表現とカッコよくいってますが，実際はタプルやリスト，基本型が再帰的に組み合わさっているだけです．

# 次記事の予定・あとがき
メタプロは本来，コードの自動生成に役立つものです．
なので，次の記事は紹介したネタを元にコードの自動置換に触れていく予定です．
自動置換が難しかったので，本記事を入門編的な位置付けにしました．

# 最後に
**※マクロは用法用量を守って正しくお使いください．**

---

fukuoka.ex Elixir／Phoenix Advent Calendar 2020 の6日目は @yoshitia さんの「[やがてelixirになる](https://qiita.com/yoshitia/items/5ad36ef83ad69a564970#_reference-dfc0db20fe39ff6e3075)」です．お楽しみに！