Reputation: 14744
I know I'm a little bit Ruby-ish or even Java-ish here but here's the case I encounter very often.
I want to define some general module (metaprogramming can be used) which will use some variables/function that will be specified in "children" modules (which will probably use
this general module).
I imagine it like:
defmodule Parent do
defmacro __using__(_) do
quote do
def function do
__MODULE__.other_function
end
end
end
end
and later on:
defmodule Child do
use Parent
def other_function do
# some real work
end
end
I very often need to "abstract" some function and the best it would be by defining some module-variable @var
accessible in "parent" module but I know that it doesn't work like that whatsoever.
Is there any way to call a function that will be defined in "including" module?
Upvotes: 3
Views: 882
Reputation: 1591
I think you're on the right track. If you add a @callback
to define the spec
for the "abstract" function it would be better. I'll explain with a working example:
defmodule Fibonacci do
@callback fib(number :: integer) :: integer
defmacro __using__(_) do
quote do
@behaviour Fibonacci
def fibonacci(n) when not is_integer(n), do: {:error, "Not integer"}
def fibonacci(n) when n < 0, do: {:error, "Negative number"}
def fibonacci(n), do: {:ok, __MODULE__.fib(n)}
def fibonacci!(n) do
case fibonacci(n) do
{:error, reason} -> raise reason
{:ok, n} -> n
end
end
end
end
end
Every module that uses Fibonacci
will need to implement the callback function fib/1
. Also every module that use
the Fibonacci
module will have the function fibonacci/1
that handles errors and fibonacci!/1
that raises errors.
So, let's implement fib/1
with direct recursion:
defmodule Direct do
use Fibonacci
def fib(0), do: 0
def fib(1), do: 1
def fib(n), do: fib(n - 1) + fib(n - 2)
end
And for tail recursion:
defmodule Tail do
use Fibonacci
def fib(0), do: 0
def fib(1), do: 1
def fib(n), do: fib(1, 1, n - 2)
defp fib(_, value, 0), do: value
defp fib(n_2, n_1, n), do: fib(n_1, n_2 + n_1, n - 1)
end
So in iex
we can call the different implementations:
iex(1)> Direct.fibonacci(10)
{:ok, 55}
iex(2)> Tail.fibonacci!(10)
55
iex(3)> Tail.fibonacci!(-10)
** (RuntimeError) Negative number
Also, if you forget to define the fib/1
function in your module, then the compiler will warn you about it:
defmodule Warning do
use Fibonacci
end
* warning: undefined behaviour function fib/1 (for behaviour Fibonacci)
If you want to play with this code, just follow this link http://elixirplayground.com?gist=606fe8c283443f03c7af08f85c888fe3
I hope this answers your question.
Upvotes: 3
Reputation: 9289
There is probably more than one way to do it depending on your exact use case. The simplest thing to do is to just use higher order functions and no metaprogramming at all.
defmodule Parent do
def function(function_passed_as_variable) do
function_passed_as_variable.()
end
end
defmodule Child do
def function do
Parent.function(&other_function/1)
end
def other_function do
#some real function
end
end
I kept the names Parent
and Child
to refer to your code, but in this situation it doesn't fit.
To achieve polymorphism (one function doing different things depending on data), you could use pattern matching:
def function({:first_type, data}), do: data*2
def function({:second_type, data}), do: data+2
def function(data), do: data
There are also protocols which can be defined for different structs and other datatypes.
It might not be easy to choose the right pattern so feel free to comment on more specific example.
Upvotes: 2