発端
RubyやRailsの使い慣れたEnumerableをElixirでも利用できるようにしようと思ったのが始まりです。 ※ 現在実装中
要件として、ElixirのEnum挙動を担保したかった為、全てのmoduleのfunctionsをそっくりそのままコピーする必要がありました。
実装
Elixirのメタプログラミングである quote
や unquote
を用います。
defmodule AllFunctionsDelegator do
def delegate!(mod) do
enum_funs =
mod.module_info()[:exports]
|> Enum.filter(fn {fun, _} -> fun not in [:__info__, :module_info] end)
for {fun, arity} <- enum_funs do
quote do
defdelegate unquote(fun)(unquote_splicing(make_args(arity))), to: unquote(mod)
end
end
end
def make_args(0), do: []
def make_args(n) do
Enum.map(1..n, fn n -> {String.to_atom("arg#{n}"), [], Elixir} end)
end
end
参考: https://elixirforum.com/t/batch-delegate-functions-to-a-module/25046
使用例
defmodule EnumFunctionsDelegator do
defmacro __using__(_opts) do
AllFunctionsDelegator.delegate!(Enum)
end
end
defmodule ExtendedEnum do
use EnumFunctionsDelegator
def customized_map(enumerable, func) do
map(enumerable, func) ++ [:customized]
end
end
# 拡張がちゃんと行えている
iex(1)> [1, 2]
[1, 2]
iex(2)> |> ExtendedEnum.customized_map(&(&1*2))
[2, 4, :customized]
# Enumの機能も担保している
iex(3)> [1, 2]
[1, 2]
iex(4)> |> ExtendedEnum.map(&(&1*2))
[2, 4]
終わりに
既存実装の挙動を担保したい
この要件は実務やLibrary制作でちょいちょい発生しそうです。
ちょっとゴリッとしたい時にとても便利そうです。