Intro

Whether you call it module extension, module inheritance, or module subclassing, it seems like it's not considered very idiomatic Elixir (ref).

Nevertheless, module extension is a natural thing for people to be curious about coming from other languages, and there's probably some special use cases where it's better than the alternative. How best to accomplish it?

Slightly Evil Code

As mentioned in the mailing list conversation referenced above, this gist demonstrates a (mostly) working hack that does the trick, but you might need small modifications for different versions of Elixir. Below I've changed the original binary_to_atom call and added usage examples to discuss.

## extension.ex

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) ->
                 { :erlang.binary_to_atom((<< ?x, ?A + i - 1 >>), :utf8), [], nil }
               end
             end
      { name, [], args }
    end
    quote do
      defdelegate unquote(signatures), to: unquote(module)
      defoverridable unquote(functions)
    end
  end
end

## Another file, demo.exs

require Extension

defmodule InheritedDemo1 do
  def inherited() do
    :result
  end
  def override() do
    :original
  end
end

defmodule ModuleExtension do
  Extension.extends(InheritedDemo1)
  def override() do
    :new
  end
end

InheritedDemo1.inherited() == :result  # case 1: true
ModuleExtension.inherited() == :result # case 2: true
ModuleExtension.override() == :new     # case 3: true

Everything here works as expected for the test-cases, but the implementation is pretty gnarly. To shed some light on how it works, the docs for defdelegate and defoverridable are good starting places.

More Idiomatic

The code below is an attempt at something more idiomatic, but as shown in the test cases at the bottom of the snippet, the semantics of what "extension" means are a little different.

defmodule InheritedDemo2 do
  defmacro __using__(_) do
    quote do
      def inherited(), do: :result
      def override(), do: :original
    end
  end
end

defmodule ModuleExtension do
  use InheritedDemo2
  def override(), do: :new
end


InheritedDemo2.inherited()             # case 1: error
ModuleExtension.inherited() == :result # case 2: true
ModuleExtension.override() == :new     # case 3: false

The third test case in particular is surprising, but actually ModuleExtension.override() == :original. There's no compile warning or anything that I've just tried to override the unoverridable!

The way to fix this is def overridable, described in the docs here, where the integer arguments you see are function arity.

defmodule InheritedDemo3 do
  defmacro __using__(_) do
    quote do
      def inherited(), do: :result
      def override(), do: :original
      defoverridable [override: 0]
    end
  end
end

defmodule ModuleExtension do
  use InheritedDemo3
  def override(), do: :new
end


InheritedDemo3.inherited()             # case 1: error
ModuleExtension.inherited() == :result # case 2: true
ModuleExtension.override() == :new     # case 3: true

The only failing case now is the first one, which is because InheritedDemo3.inherited/0 is NOT defined in the module per se, it is only mentioned inside of the using macro. Fixing this last case is awkward, and considering precedents with ABCs, interfaces, etc in other languages, the compromise seems acceptable.