Deco v0.1.2 Deco
Deco - Minimalist Function Decorators for Elixir.
Yes, yet another package for decorating elixir functions :).
However, deco
’s core is minimalist in comparission to others,
uses much less magic, does not overrides anything like Kernel.def
nor any operator.
deco
has only one macro, decorators themselves are just plain functions
Macro.t() -> Macro.t()
, and imposes no run-time overhead because
function decoration is performed at compile-time.
Usage
use Deco
The syntax is deco DECORATORS in FORM
where decorators is a tuple of
one or more decorators, and form is tipically a function definition.
decorators
are just plain functions you write that take the AST of
FORM
and just return a modified AST of it. The Deco
module has
some convenience functions for updating the AST.
Since deco
runs at compile time, you cannot use functions being
defined in the same module that is using deco
. Normally it’s better
to define your decorators on a separate module.
The following example decorates the function with a
tracer
that will use Logger
to print the arguments given before applying
the original function, and the its final result.
deco { Trace.trace() } in
def foo(x) do
x
end
Decorators can take arguments, the AST will be prepended to the list of arguments given by you.
Also, because decorators operate on the AST they have full access to it allowing them to for example, introduce new guards or remove them.
deco { Deco.update_guard(fn _ -> nil end) } in
def foo(x) when is_atom(x) do
x
end
foo("hey")
=> "hey"
Decorators can be composed, the one at the end will take the original AST and produce a new one for the one on top of it.
deco {
Deco.pipe_result(String.capitalize),
Deco.pipe_result(String.reverse),
Deco.pipe_result(to_string)
} in
def foo(x) do
x
end
foo(:john)
=> "Nhoj
You can decorate using a simple function, without having to mess with
the AST if you dont want to. The Deco.around
decorator will act as
an around advice and will give you a reference to the decorated
function (made private) and all the arguments given on invocation.
# our private around advice
defp bar_wrapper(decorated, name, say) do
"#{say} #{decorated.(name)}"
end
deco {Deco.around( bar_wrapper("hello") )} in
def bar(name) do
name |> String.capitalize
end
bar("world")
=> "hello World"
For more examples, see the tests and the use the source, Luke
AuthDeco
This example was adapted from arjan/decorator to show how it would look like using deco.
defp is_authorized(decorated, conn, params) do
if conn.assigns.user do
decorated.(conn, params)
else
conn
|> send_resp(401, "unauthorized")
|> halt()
end
end
deco {Deco.around(is_authorized)} in
def create(conn, params) do
...
end
Installation
def deps do
[
{:deco, "~> 0.1"}
]
end
Link to this section Summary
Functions
Wraps the decorated function invocation with another call
Creates a list of fresh variables, one per each formal argument in head
Get the head of the function definition. name(args)
Generates and binds a new variable to each formal parameter
Pipes the result of the decorated function into pipe
Make the function definition private, prefixing its name with prefix
Alias for update_do
Lets you update the decorated function body
Lets you update or remove the function definition guard
Lets you update the function definition head
Link to this section Types
Link to this section Functions
Wraps the decorated function invocation with another call.
The wrapper function will take as arguments: a function reference to the decorated function, all the arguments given to the decorated function invocation and all the arguments given in the decorator declaration.
defp bar_wrapper(decorated, name, say) do
"#{say} #{decorated.(name)}"
end
deco {Deco.around( bar_wrapper("hello") )} in
def bar(name) do
name |> String.capitalize
end
bar("world")
=> "hello World"
This function works by making the original function definition private and calling your wrapper with a reference to the private function, this way you can alter or prevent the original function invocation like an around advice.
Creates a list of fresh variables, one per each formal argument in head.
Get the head of the function definition. name(args)
Generates and binds a new variable to each formal parameter
Returns a tuple of the updated defun and a list of the bound argument variables.
Pipes the result of the decorated function into pipe
.
Example
deco {Deco.pipe_result(to_string)} in
def foo(x), do: x
foo(:hello)
=> "hello"
Make the function definition private, prefixing its name with prefix
Alias for update_do
Lets you update the decorated function body
Note that updater
function takes the quoted
body and should also return another quoted
expression.
deco {Deco.update_do(fn _ -> :yes end)}
def foo, do: :ok
foo()
=> :yes
Using this decorator you can rescue exceptions and turn them into erlang tagged tuples:
defmodule MyDecos do
def ok_or_error(defun) do
defun |> Deco.update_do(fn body ->
quote do
try do
{:ok, unquote(body)}
rescue
error -> {:error, error}
end
end
end)
end
end
deco {MyDecos.ok_or_error} in
def foo() do
raise "Oops"
end
foo()
=> {:error, %RuntimeError{message: "Oops"}}
Lets you update or remove the function definition guard.
The updater
function will receive the AST of the current guard
or nil if none was defined.
If nil is returned by updater
, the guard is removed from
the decorated function.
Lets you update the function definition head