Exaop v0.1.0 Exaop View Source
A minimal library for aspect-oriented programming.
Unlike common AOP patterns, Exaop does not introduce any additional behavior to existing functions, as it may bring complexity and make the control flow obscured. Elixir developers prefer explicit over implicit, thus invoking the cross-cutting behavior by simply calling the plain old function generated by pointcut definitions is better than using some magic like module attributes and macros to decorate and weave a function.
Examples
Here's a simple example of using Exaop:
defmodule Foo do
use Exaop
check :validity
set :compute
@impl true
def check_validity(%{b: b} = _params, _args, _acc) do
if b == 0 do
{:error, :divide_by_zero}
else
:ok
end
end
@impl true
def set_compute(%{a: a, b: b} = _params, _args, acc) do
Map.put(acc, :result, a / b)
end
end
A function __inject__/2
is generated in the above module Foo
. When it is
called, the callbacks are triggered in the order defined by your pointcut
definitions.
Throughout the execution of the pointcut callbacks, an accumulator is passed and updated after running each callback. The execution process may be halted by a return value of a callback.
If the execution is not halted by any callback, the final accumulator value is
returned by the __inject__/2
function. Otherwise, the return value of the
callback that terminates the entire execution process is returned.
In the above example, the value of the accumulator is returned if the
check_validity
is passed:
iex> params = %{a: 1, b: 2}
iex> initial_acc = %{}
iex> Foo.__inject__(params, initial_acc)
%{result: 0.5}
The halted error is returned if the execution is aborted:
iex> params = %{a: 1, b: 0}
iex> initial_acc = %{}
iex> Foo.__inject__(params, initial_acc)
{:error, :divide_by_zero}
Pointcut definitions
check :validity
set :compute
We've already seen the pointcut definitions in the example before.
check_validity/3
and set_compute/3
are the pointcut callback functions
required by these definitions.
Additional arguments can be set:
check :validity, some_option: true
set :compute, {:a, :b}
Pointcut callbacks
Naming and arguments
All types of callbacks have the same function signature. Each callback function following the naming convention in the example, using an underscore to connect the pointcut type and the following atom as the callback function name.
Each callback has three arguments and each argument can be of any Elixir term.
The first argument of the callback function is passed from the first argument
of the caller __inject__/2
. The argument remains unchanged in each callback
during the execution process.
The second argument of the callback function is passed from its pointcut
definition, for example, set :compute, :my_arg
passes :my_arg
as the
second argument of its callback function set_compute/3
.
The third argument is the accumulator. It is initialized as the second
argument of the caller __inject__/2
. The value of accumulator is updated or
remains the same after each callback execution, depending on the types and
the return values of the callback functions.
Types and behaviours
Each kind of pointcut has different impacts on the execution process and the accumulator.
Exaop ships with three pointcut macros:
View documentation of these macros for details.
Link to this section Summary
Functions
Allows to decide whether to terminate the entire execution process of the
generated __inject__/2
function. It does not change the value of the
accumulator.
Allows to update the value of the accumulator or halt the execution process.
Allows to update the value of the accumulator, setting the accumulator to the return value of its callback. It does not halt the execution process.
Link to this section Types
Link to this section Functions
Allows to decide whether to terminate the entire execution process of the
generated __inject__/2
function. It does not change the value of the
accumulator.
The execution of the generated function is halted if its callback return
value matches the pattern {:error, _}
. The execution continues if its
callback returns :ok
.
Allows to update the value of the accumulator or halt the execution process.
The execution of the generated function is halted if its callback return
value matches the pattern {:error, _}
. The accumulator is updated to the
wrapped acc
if its callback return value matches the pattern {:ok, acc}
.
Allows to update the value of the accumulator, setting the accumulator to the return value of its callback. It does not halt the execution process.