LoginSignup
10
3

More than 1 year has passed since last update.

Elixirでuseされた時に全ての関数を移譲するというパワー系コードを書いてみました。

Last updated at Posted at 2022-01-06

発端

RubyやRailsの使い慣れたEnumerableをElixirでも利用できるようにしようと思ったのが始まりです。 ※ 現在実装中

要件として、ElixirのEnum挙動を担保したかった為、全てのmoduleのfunctionsをそっくりそのままコピーする必要がありました。

実装

Elixirのメタプログラミングである quoteunquote を用います。

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制作でちょいちょい発生しそうです。
ちょっとゴリッとしたい時にとても便利そうです。

10
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
3