Goal
- マクロのさび落とし
- 見た目の記法だけChankoっぽいもの(偽物)を実装する(内部実装は・・・)
Dev-Environment
- OS: Windows8.1
- Erlang: Eshell V7.3, OTP-Version 18.3
- Elixir: v1.2.3
Play with the Elixir-Macro
A/Bテストをやろうと思っていたのに、気が付いたらマクロでやっていました。
(機能の一部で)見た目の記述だけChankoっぽいもの(完全な偽物)を実装してみました。
(機能の一部で)見た目の記述だけChankoっぽいもの(完全な偽物)を実装してみました。
あくまで使うときの見た目だけなので、実装に関しては残念ながら微妙です。
結局、内部でtry~rescueとifを使って処理しています。
本気でやるならブラッシュアップが必要ですね。
最初のとっかかりとしては、まぁこんなもんでしょう。(残当)
結局、内部でtry~rescueとifを使って処理しています。
本気でやるならブラッシュアップが必要ですね。
最初のとっかかりとしては、まぁこんなもんでしょう。(残当)
それでは、やっていきます。
Module: Unit
テスト用のモジュールで使うマクロを定義したモジュールです。
- active_if/1: 実行するのかしないのかのフラグを返すだけの関数をマクロで展開しているだけです。
- function/2: 関数を動的にマクロで展開しているだけです。func_nameが関数名になります。
File: unit.ex
defmodule Unit do
defmacro __using__(_options) do
quote do
import unquote(__MODULE__)
end
end
defmacro active_if(condition) do
quote do
def active? do
unquote(condition)
end
end
end
defmacro function(func_name, clauses) do
if func_name do
quote do
def unquote(String.to_atom "#{func_name}")() do
Keyword.get(unquote(clauses), :do, nil)
end
end
end
end
end
Module: TestControllerUnit
サンプルとしてUnitモジュールを使ってみたモジュールです。
Unitモジュールを実際に使ってみるとこんな感じになります。
Unitモジュールを実際に使ってみるとこんな感じになります。
File: test_controller_unit.ex
defmodule TestControllerUnit do
use Unit
active_if true
function(:show) do
d = 5
e = d + 6
f = e + 7
end
end
Module: Helper
補助関数を定義しているモジュールです。
- exec/2, exec/3, atom_to_module_string/1: モジュールがatomのままだとapplyで実行できないため、補助関数として作りました。
File: helper.ex
defmodule Helper do
def exec(module, fun) do
exec(module, fun, [])
end
def exec(module, fun, []) do
atom_to_module_string(module) |> String.to_atom |> apply(fun, [])
end
def exec(module, fun, args) when is_list(args) do
atom_to_module_string(module) |> String.to_atom |> apply(fun, args)
end
def atom_to_module_string(atom) do
"Elixir." <> (Atom.to_string(atom) |> Mix.Utils.camelize)
end
end
Module: Invoker
結局、内部でtry~rescueとifを使って、
実コードにてマクロが展開されているだけになってしまいました。
実コードにてマクロが展開されているだけになってしまいました。
- invoke/3: テスト対象のモジュールと関数を指定して、デフォルトの処理をdo~endで記述する。
File: invoker.ex
defmodule Invoker do
defmacro __using__(_options) do
quote do
import unquote(__MODULE__)
import Helper
end
end
defmacro invoke(module_name, func_name, clauses) do
quote do
do_clause = Keyword.get(unquote(clauses), :do, nil)
try do
if exec(unquote(module_name), :active?) do
exec(unquote(module_name), unquote(func_name))
else
do_clause
end
rescue
_any -> do_clause
end
end
end
end
Module: TestController
サンプルとしてInvokerモジュールを使ってみたモジュールです。
Invokerモジュールを実際に使ってみるとこんな感じになります。
Invokerモジュールを実際に使ってみるとこんな感じになります。
File: test_controller.ex
defmodule TestController do
use Invoker
def show do
invoke(:test_controller_unit, :show) do
a = 1
b = a + 2
c = b + 3
end
end
end
Example:
## active_if: true
iex> TestController.show
18
## active_if: false
iex> TestController.show
6
TestControllerUnitでraiseを起こしてみると、rescueの処理が実行される。
Example:
defmodule TestControllerUnit do
...
function(:show) do
d = 5
e = d + 6
raise "oops"
f = e + 7
end
end
iex> TestController.show
6
もう少しtry~rescueやifのあたりを何とかしたかったのですが、
マクロの動作に翻弄されて、そこまで手が回りませんでした。
あまり参考になるコードではないと思いますが、誰かの役に立ったなら幸いです。m( )m
マクロの動作に翻弄されて、そこまで手が回りませんでした。
あまり参考になるコードではないと思いますが、誰かの役に立ったなら幸いです。m( )m