I wrote a module that will handle what you’re looking for with only a little extra code:
defmodule AndAlso do
defmacro __using__(arg) do
quote bind_quoted: [module: arg] do
module
|> AndAlso.to_delegate()
|> Enum.each(fn {name, arity} ->
fun = AndAlso.make_fun(name, arity)
defdelegate unquote(fun), to: module
defoverridable [{name, arity}]
end)
end
end
def to_delegate(module) do
module.module_info(:exports)
|> Enum.reject(&is_underscored?/1)
|> Enum.reject(&is_module_info?/1)
end
defp is_underscored?({name, _arity}) do
name
|> Atom.to_string()
|> String.starts_with?("__")
end
defp is_module_info?({name, _arity}) do
name == :module_info
end
def make_fun(name, arity) do
{name, [], make_args(arity)}
end
defp make_args(arity) do
Enum.map(1..arity, &{String.to_atom("x#{&1}"), [], Elixir})
end
end
then you use it like this:
defmodule Example do
def add(a, b), do: a + b
end
defmodule Example2 do
use AndAlso, Example
def sub(a, b), do: a - b
end
Example2.add(1, 2)
# => 3
defmodule Example3 do
use AndAlso, Example2
def add(a, b), do: a + 2*b
end
Example3.add(1, 2)
# => 5
Some things to be aware of:
-
I have no idea what the implications of doing this for performance are. They’re probably not huge, but measure them before using this in anything besides a Livebook
-
using AndAlso
on modules that define functions prefixed with __
will not work like you expect. For instance, a module that calls defstruct
or says use Ecto.Schema
-
while you’re normally allowed to redefine modules, the way this macro generates code requires that “old” versions have distinct names - doing this will not work:
defmodule Example do
def add(a, b), do: a + b
end
defmodule Example do
use AndAlso, Example
def sub(a, b), do: a - b
end
it fails with (on 1.14-otp-25):
** (ArgumentError) defdelegate function is calling itself, which will lead to an infinite loop. You should either change the value of the :to option or specify the :as option
(elixir 1.14.0) lib/kernel/utils.ex:32: Kernel.Utils.defdelegate_all/3
iex:87: anonymous fn/1 in :elixir_compiler_10.__MODULE__/1
(elixir 1.14.0) lib/enum.ex:975: Enum."-each/2-lists^foreach/1-0-"/2
iex:87: (module)
(elixir 1.14.0) src/elixir_compiler.erl:65: :elixir_compiler.dispatch/4
(elixir 1.14.0) src/elixir_compiler.erl:50: :elixir_compiler.compile/3
(elixir 1.14.0) src/elixir_module.erl:379: :elixir_module.eval_form/6
iex:86: (file)