Skip to content

Instantly share code, notes, and snippets.

@orenbenkiki
Last active September 12, 2024 04:08
Show Gist options
  • Save orenbenkiki/5174435 to your computer and use it in GitHub Desktop.
Save orenbenkiki/5174435 to your computer and use it in GitHub Desktop.
Elixir module inheritance
defmodule Extension do
defmacro extends(module) do
# As above...
end
defmacro implements(module, protocol: protocol) do
quote do
defimpl unquote(protocol), for: unquote(module) do
import Extension
extends unquote(module)
end
end
end
end
Code.require_file "../test_helper.exs", __FILE__
import Extension
defprotocol Foo do
def direct(this)
def wrapper(this, arg)
end
defmodule Bar do
@behaviour Foo
def direct(_this) do
:foo
end
def wrapper(this, arg) do
[ Foo.direct(this), arg ]
end
end
implements Bar, protocol: Foo
defmodule Baz do
@behaviour Foo
import Extension
extends Bar
def direct(_this) do
:bar
end
end
implements Baz, protocol: Foo
defmodule ExtensionTest do
use ExUnit.Case, async: true
test "foo" do
assert Foo.direct({Bar}) == :foo
assert Foo.wrapper({Bar}, 0) == [ :foo, 0 ]
end
test "bar" do
assert Foo.direct({Baz}) == :bar
assert Foo.wrapper({Baz}, 0) == [ :bar, 0 ]
end
end
defmodule Extension do
defmacro extends(module) do
module = Macro.expand(module, __CALLER__)
functions = module.__info__(:functions)
signatures = Enum.map functions, fn { name, arity } ->
args = if arity == 0 do
[]
else
Enum.map 1 .. arity, fn(i) ->
{ binary_to_atom(<< ?x, ?A + i - 1 >>), [], nil }
end
end
{ name, [], args }
end
quote do
defdelegate unquote(signatures), to: unquote(module)
defoverridable unquote(functions)
end
end
end
Code.require_file "../test_helper.exs", __FILE__
defmodule Foo do
def direct do
:foo
end
def wrapper do
direct
end
end
defmodule Bar do
import Extension
extends Foo
def direct do
:bar
end
end
defmodule Baz do
import Extension
extends Bar
def direct do
:baz
end
def wrapper do
direct
end
end
defmodule TodoxTest do
use ExUnit.Case, async: true
test "foo" do
assert Foo.direct == :foo
assert Foo.wrapper == :foo
end
test "bar" do
assert Bar.direct == :bar
assert Bar.wrapper == :bar # Actually returns :foo
end
test "baz" do
assert Baz.direct == :baz
assert Baz.wrapper == :baz
end
end
@HWideman
Copy link

Thanks for this! I was able to edit the Extension module so it did not throw a warning in newer versions of Elixir like so:

defmodule Extension do
  defmacro extends(module) do
    module = Macro.expand(module, __CALLER__)
    functions = module.__info__(:functions)
    signatures = Enum.map functions, fn { name, arity } ->
      args = if arity == 0 do
               []
             else
               Enum.map 1 .. arity, fn(i) ->
                 { String.to_atom(<< ?x, ?A + i - 1 >>), [], nil }
               end
             end
      { name, [], args }
    end

    zipped = List.zip([signatures, functions])
    for sig_func <- zipped do
      quote do
        defdelegate unquote(elem(sig_func, 0)), to: unquote(module)
        defoverridable unquote([elem(sig_func, 1)])
      end
    end
  end
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment