本記事は、Elixir Advent Calendar 2024 シリーズ7の19日目です。
変数束縛とパイプライン
Elixirのパイプラインは書いていて楽しいものですが、たまーに変数をおいておかないといけないこともありますよね?
data1 =
big_data
|> something_process()
data2 =
big_data
|> another_process()
Enum.zip(data1, data2)
|> hoge_process
...
このように変数の列とデータ処理の列がずれます。
これはこれで変数が強調されるので見やすいという意見は当然あり得ます。
が、なんとなく揃えたいなーというのが本記事の動機です。
完成系
下記のように追加した構文bind
が動くことを目指します。
[1, 2, 3]
|> Enum.map(& &1 * 2)
|> bind(:a)
IO.inspect(a)
#=> [2, 4, 6]
説明
最終的には下記2つの要件を満たす必要があります。
- 変数の用意: 「変数を用意する」という関数の呼び出し
- 式を追加: 元の計算式に加えて「変数を用意する」という式を追加
変数の用意
結論を書くと、Macro.var/2
を使えば可能です。
引数1は変数名、引数2はcontext
と記載されてますが、変数をどのモジュールや関数で定義するか、という情報だと理解すればOK。
livebookやiexだと実行はできますが、効果は発揮できません。
戻り値はElixirにおける内部のコード表現であるASTになっています。
iex> Macro.var(:foo, __MODULE__)
#=> {:foo, [], nil}
式を追加
元の式を書き換える機能といえば、Elixirマクロの出番です。
前項で出てきた変数の内部表現を使ってElixirの式を改変して返します。
実装
下記が最終系です。
livemd
# Bind Macro for Qiita
## Section
```elixir
defmodule Util do
defmacro bind(ast, var) do
# 変数の内部表現
var = Macro.var(var, __CALLER__.module)
# デバッグ用
ast
|> Macro.to_string()
|> IO.puts()
|> dbg()
# 作った変数 = 元のASTという形にして返す
quote do
unquote(var) = unquote(ast)
end
end
end
```
```elixir
require Util
import Util
[1, 2, 3]
|> Enum.map(& &1 * 2)
|> bind(:a)
```
解説
- CALLERはマクロの呼び出し元の情報を取得します(livebookだと
__CALLER__.module
はnilです) - quoteで元の式を返します
- unquoteでAST表現されているコードをElixirコードにします
- (メタプロ基本はElixirSchoolを読むのがおすすめ)
パイプラインから続けて変数束縛できるようにしました。