Создание программ, которые порождают другие программы как результат своей работы (на стадии компиляции), либо программ, которые меняют себя во время выполнения (самомодифицирующийся код).
© Wikipedia
Конечное, ориентированное дерево, в котором внутренние вершины сопоставлены с операторами ЯП, а листья — с соответствующими операндами.
© Wikipedia
b*b - 4*a*c
defmodule
/ def
/ defp
defstruct
/ defrecord
defprotocol
/ defimpl
defoverridable
/ defdelegate
if
/ unless
with
/ for
alias
/ require
/ import
quote
преобразует выражение в AST
unquote
вставляет значение внутрь quote-блока
unquote_splicing
вставляет список как набор значений внутрь quote-блока
quote
AST-представление выражения в Elixir - это кортеж.
Простое выражение:
iex(1)> quote do: rem(5, 2)
{:rem, [context: Elixir, import: Kernel], [5, 2]}
И чуть посложнее:
iex(2)> quote do
...(2)> if age >= 18 do
...(2)> :ok
...(2)> else
...(2)> :error
...(2)> end
...(2)> end
{:if, [context: Elixir, import: Kernel],
[{:>=, [context: Elixir, import: Kernel], [{:age, [], Elixir}, 18]},
[do: :ok, else: :error]]}
unquote
iex(1)> a = 7
iex(2)> ast = quote do
...(2)> a * 6
...(2)> end
{:*, [context: Elixir, import: Kernel], [{:a, [], Elixir}, 6]}
iex(3)> Code.eval_quoted(ast)
** (CompileError) nofile:1: undefined function a/0
iex(1)> a = 7
iex(2)> ast = quote do
...(2)> unquote(a) * 6
...(2)> end
{:*, [context: Elixir, import: Kernel], [7, 6]}
iex(3)> Code.eval_quoted(ast)
{42, []}
iex(1)> a = 7
iex(2)> "#{a} * 6"
"7 * 6"
unquote_splicing
iex(1)> args = ["a,b,c", ","]
iex(2)> ast = quote do
...(2)> String.split(unquote(args))
...(2)> end
iex(3)> Code.eval_quoted(ast)
** (FunctionClauseError) no function clause matching in String.Break.split/1
(elixir) unicode/unicode.ex:428: String.Break.split(["a,b,c", ","])
iex(1)> args = ["a,b,c", ","]
iex(2)> ast = quote do
...(2)> String.split(unquote_splicing(args))
...(2)> end
iex(3)> Code.eval_quoted(ast)
{["a", "b", "c"], []}
defmacro list_to_tuple(list) do
quote do
{ unquote_splicing(list) }
end
end
list_to_tuple([1, 2, 3]) # => {1, 2, 3}
__using__
defmodule MyApp.Company do
use MyApp.Web, :model # invokes __using__ hook
# ...
end
# web/web.ex
defmodule MyApp.Web
defmacro __using__(which) when is_atom(which) do
apply(__MODULE__, which, [])
end
def model do
quote do
use Ecto.Schema
import Ecto
import Ecto.Changeset
import Ecto.Query
end
end
# ...
end
Ожидаемый результат:
def super_function do
# some code
# ...
newrelic_trace("PageController.index", :other, "super_function") do
# потенциально медленный код
end
end
defmacro newrelic_trace(transaction, category, segment_name, do: block) do
quote do
# вычисляем реальное значение блока и время его исполнения
{elapsed, result} = :timer.tc fn -> unquote(block) end
data = {unquote(category), unquote(segment_name)}
NewRelic.Collector.record_value({unquote(transaction), data}, elapsed)
# возвращаем значение исходного блока
result
end
end
Примеры: список квадратов и сумма квадратов
Enum.map(1..5, fn(x) -> x*x end) # => [1, 4, 9, 16, 25]
Enum.reduce(1..5, 0, fn(x, acc) -> acc+x*x end) # => 55
как-то покороче
Enum.map(1..5, &(&1*&1)) # => [1, 4, 9, 16, 25]
Enum.reduce(1..5, 0, &(&2+&1*&1)) # => 55
a что если так?
Enum.map(1..5, x ~> x*x) # => [1, 4, 9, 16, 25]
Enum.reduce(1..5, 0, {x, acc} ~> acc+x*x) # => 55
defmacro args ~> body do
args = case args do
# {x} ~> x+1
{:{}, _, arg} -> arg
# x ~> x+1
{var, _, nil} when is_atom(var) -> [args]
# {x, y} ~> x+y
_ -> Tuple.to_list(args)
end
{:fn, [], [{:->, [], [args, body]}]}
end
Ожидаемый результат:
html_fragment do
h1 do: "Header"
div do
p do: "foo"
br
p do: "bar"
end
end
# => "<h1>Header</h1><div><p>foo</p><br /><p>bar</p></div>"
def tag(name, self_closing, do: content) do
if self_closing do
"<#{name} />"
else
"<#{name}>#{content}</#{name}>"
end
end
defmacro html_fragment(do: ast) do
new_ast = Macro.prewalk(ast, &substitute_html_elements/1)
quote do
to_string(unquote(new_ast))
end
end
defp substitute_html_elements({:__block__, _, elems}) do
elems
end
defp substitute_html_elements({tag_name, meta, args}) do
cond do
tag_name in [:h1, :p, :div, :table, :tr, :td] ->
{:tag, meta, [tag_name, false | args]}
tag_name in [:br, :hr] ->
{:tag, meta, [tag_name, true, [do: nil]]}
true ->
{tag_name, meta, args}
end
end
defp substitute_html_elements(other) do
other
end
Полный код примера: https://github.com/romul/html_fragments
Большие возможности - это и большая ответственность.
Не пишите макросы там, где можно обойтись обычными функциями.