この記事では、Elixirの関数に付加的な機能を追加するライブラリである decorator について紹介します。
decoratorは、あらかじめ定義された処理を任意の関数へと追加するライブラリです。これにより、デバッグ用途の情報取得処理や何らかのチェック処理を簡単に追加することが可能になります。
使い方
それでは、decorator の使用方法を見てみましょう。
インストール
mix.exs
の deps
に decorator
を追加して mix deps.get
を実行します。
defp deps do
[
{:decorator, "~> 1.2"}
]
end
基本的な使い方
decorator は、次のように Decorator.Define
マクロを使用して定義します。この例では、関数の情報やパラメータを出力する print_func_info
という decorator を定義しています。
defmodule PrintDecorator do
use Decorator.Define, print_func_info: 0
def print_func_info(body, context) do
quote do
# moduleや関数名などの情報を取り出す
module = Atom.to_string(unquote(context.module))
name = Atom.to_string(unquote(context.name))
arity = unquote(context.arity)
args = unquote(context.args)
# 取得した情報を表示する
IO.puts("[DEV] #{module}.#{name}/#{arity} is called.")
IO.write("[DEV] args: ")
IO.inspect(args)
unquote(body)
end
end
end
定義した print_func_info
は、次のコードのように @decorate
と共に使用します。ここでは Greeting.say_goodbye/2
という関数に、情報出力機能を追加しています。
defmodule Greeting do
use PrintDecorator
@decorate print_func_info()
def say_goodbye(name, time) do
IO.puts("It's #{time}. Goodbye #{name}.")
end
end
iexで Greeting.say_goodbye/2
を実行すると、次のように出力されます。
iex(1)> Greeting.say_goodbye("Taro", "18:00")
[DEV] Elixir.Greeting.say_goodbye/2 is called.
[DEV] args: ["Taro", "18:00"]
It's 18:00. Goodbye Taro.
:ok
パラメータチェックの例
他の使用例として、関数へと渡されるパラメータの値をチェックさせる例を見てみましょう。
ここでは、パラメータの値に nil
が含まれていれば :error
を返す decorator を考えてみます。そのような動作をさせるため、次のように check_param_not_nil
という decorator を定義します。
defmodule ParameterDecorator do
use Decorator.Define, check_param_not_nil: 0
def check_param_not_nil(body, context) do
quote do
# パラメータにnilが含まれるかチェック
args = unquote(context.args)
has_nil = args |> Enum.any?(fn x -> x == nil end)
if(has_nil) do
# nilがあれば :error を返す
{:error, :nil_param, args}
else
unquote(body)
end
end
end
end
それでは、 check_param_not_nil
を使ってみましょう。次のように、パラメータの値を加算するだけの関数 Foo.plus/3
に check_param_not_nil
をセットします。
defmodule Foo do
use ParameterDecorator
@decorate check_param_not_nil()
def plus(x, y, z) do
result = x + y + z
{:ok, result}
end
end
iexで Foo.plus/3
を実行すると、次のように出力されます。
iex(1)> Foo.plus(1, 2, 3)
{:ok, 6}
iex(2)> Foo.plus(1, nil, 3)
{:error, :nil_param, [1, nil, 3]}
まとめ
この記事では、 decorator を使うことで任意の関数に簡単に新たな機能を追加できることを紹介しました。ただし、 Elixirの公式サイト でも述べられているように、マクロの濫用はコードの可読性低下や保守性の低下を招く恐れがあります。私見ではありますが、 decorator も必要な箇所だけで適切に用いるのが良いと思います。