Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create "abstract" module in Elixir or "virtual" functions?

Tags:

elixir

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?

like image 457
Kamil Lelonek Avatar asked Oct 11 '25 22:10

Kamil Lelonek


1 Answers

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.

like image 162
Alex de Sousa Avatar answered Oct 16 '25 08:10

Alex de Sousa



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!