Let's say an elixir library defines:
defmodule Decoder do
def decode(%{"BOOL" => true}), do: true
def decode(%{"BOOL" => false}), do: false
def decode(%{"BOOL" => "true"}), do: true
def decode(%{"BOOL" => "false"}), do: false
def decode(%{"B" => value}), do: value
def decode(%{"S" => value}), do: value
def decode(%{"M" => value}), do: value |> decode
def decode(item = %{}) do
item |> Enum.reduce(%{}, fn({k, v}, map) ->
Map.put(map, k, decode(v))
end)
end
end
I want to define a module MyDecoder
which just adds one more def decode
to the above module. In an oo language, this would be done by inheritance/mixin/extends of some sort.
How do I do this in elixir?
Apparently, you can. Take a look at this gist which uses some rather "obscure" methods for listing a module's public functions and then generating delegates out of them. It's pretty cool.
Here is where it's all about:
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
You can use it like so:
defmodule MyModule do
require Extension
Extension.extends ParentModule
# ...
end
Unfortunately, it throws a warning on the most recent Elixir builds, but I'm sure that can be solved. Other than that, it works like a charm!
Edited so as not to throw a warning:
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