Deco v0.1.2 Deco

Deco - Minimalist Function Decorators for Elixir.

hexdocs.

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 type updater()
updater() :: (Macro.t() -> Macro.t())

Link to this section Functions

Link to this macro around(defun, wrapper) (macro)

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.

Link to this function fresh_args(defun, context \\ __MODULE__)

Creates a list of fresh variables, one per each formal argument in head.

Link to this function get_args(defun)
get_args(defun :: Macro.t()) :: [Macro.t()]
Link to this function get_head(defun)
get_head(defun :: Macro.t()) :: Macro.t()

Get the head of the function definition. name(args)

Link to this function get_name(defun)
get_name(defun :: Macro.t()) :: atom()
Link to this function intro_args(defun, context \\ __MODULE__)
intro_args(defun :: Macro.t(), context :: atom()) :: {Macro.t(), [Macro.t()]}

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.

Link to this macro pipe_result(defun, pipe) (macro)
pipe_result(defun :: Macro.t(), pipe :: Macro.t()) :: Macro.t()

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"
Link to this function privatize(defun, prefix)

Make the function definition private, prefixing its name with prefix

Link to this function update_args(defun, updater)
update_args(defun :: Macro.t(), updater()) :: Macro.t()
Link to this function update_body(defun, updater)
update_body(defun :: Macro.t(), updater :: updater()) :: Macro.t()

Alias for update_do

Link to this function update_do(defun, updater)
update_do(defun :: Macro.t(), updater :: updater()) :: Macro.t()

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"}}
Link to this function update_guard(defun, updater)
update_guard(defun :: Macro.t(), updater :: updater()) :: Macro.t()

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.

Link to this function update_head(defun, updater)
update_head(defun :: Macro.t(), updater :: updater()) :: Macro.t()

Lets you update the function definition head