computer programs with the ability to treat programs as their data.
class Foo
%w(foo bar baz).each do |v|
define_method(v) do
puts "Hi, I am #{v}"
end
end
end
a rule that specifies how a certain input sequence should be mapped to a replacement output sequence
Transform the program before the compiler runs
#ifdef DEBUG_BUILD
#define DEBUG(x) fprintf(stderr, x)
#else
#define DEBUG(x) do {} while (0)
#endif
(+ 1 2 3)
is
+ 1 2 3
1 2 3
It's like Lisp...
but Jose hid the parentheses
Simple expression
iex(1)> quote do: 1 + 2
{:+, [context: Elixir, import: Kernel], [1, 2]}
A little more complex expression
iex(2)> quote do
...(2)> if a > 20, do: "major", else: "minor"
...(2)> end
{:if, [context: Elixir, import: Kernel],
[{:>, [context: Elixir, import: Kernel], [{:a, [], Elixir}, 20]},
[do: "major", else: "minor"]]}
quote
transforms an expression into its AST representation`
in Lispunquote
allows to inject a value in the AST,
in Lispunquote_splicing
allows to inject an array in the AST,@
in Lispunquote
iex(1)> a = 5
iex(2)> ast = quote do
...(2)> a + 5
...(2)> end
iex(3)> Code.eval_quoted(ast)
** (CompileError) nofile:1: undefined function a/0
unquote
iex(1)> a = 5
iex(2)> ast = quote do
...(2)> unquote(a) + 5
...(2)> end
iex(3)> Code.eval_quoted(ast)
{10, []}
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
unquote_splicing
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"], []}
if
defmacro if(condition, do: do_clause, else: else_clause) do
quote do
case unquote(condition) do
x when x in [nil, false] -> unquote(else_clause)
_ -> unquote(do_clause)
end
end
end
defdelegate
defdelegate fun(a, b), to: Mod
should expand to
def fun(a, b) do
Mod.fun(a, b)
end
defdelegate
defdelegate
defmacro defdelegate(function, to: module) do
{name, _, vars} = function # {:fun, _, [{:a, _, _}, {:b, _, _}]}
quote do
def unquote(name)(unquote_splicing(vars)) do
unquote(module).unquote(name)(unquote_splicing(vars))
end
end
end
Macro.expand
Macro.to_string
var!
conn
?get "/hello" do
send_resp(conn, 200, "world")
end
defmacro get(route, do: block) do
quote do
def handle_request(conn, :get, unquote(route)) do
unquote(block)
end
end
end
# when trying to use conn
** (CompileError) iex:8: undefined function conn/0
defmacro get(route, do: block) do
quote do
def handle_request(var!(conn), :get, unquote(route)) do
unquote(block)
end
end
end
# somewhere else
get "/foo" do
IO.inspect(conn)
end
defmodule MyApp.SampleCLI do
use ExCLI.DSL
name "mycli"
description "My CLI"
long_description "This is my long description"
option :verbose, help: "Increase the verbosity level", aliases: [:v], count: true
command :hello do
description "Greets the user"
long_description """
Gives a nice a warm greeting to whoever would listen
"""
argument :name
option :from, help: "the sender of hello"
run context do
if context.verbose >= 1 do
IO.puts("Running hello command.")
end
if from = context[:from] do
IO.write("#{from} says: ")
end
IO.puts("Hello #{context.name}!")
end
end
end
use
use MyModule, opts
is equivalent torequire Module
Module.__using__(opts)
use
is (often) used to inject functionality__using__
defmacro __using__(_opts) do
quote do
import unquote(__MODULE__)
@app %{commands: []}
@command nil
@before_compile unquote(__MODULE__)
end
end
before_compile
defmacro __before_compile__(_env) do
quote do
def __app__ do
@app
end
end
end
defmacro name(name) do
quote do
@app Map.put(@app, :name, unquote(name))
end
end
defmacro command(name, do: block) do
quote do
@command %{name: unquote(name)}
unquote(block)
@app Map.put(@app, :commands, [@command | @app.commands])
@command nil
end
end
defmacro argument(name) do
quote do
if @command do
@command Map.put(@command, :arguments, [unquote(name), @command.arguments])
else
raise "argument should be called from inside a command"
end
end
end
defmacro run(context, do: block) do
quote bind_quoted: [context: Macro.escape(context), block: Macro.escape(block)] do
def __run__(unquote(@command.name), var!(unquote(context))) do
unquote(block)
end
end
end
defmodule MyCLI do
use MyDSL
name "my_cli"
command :my_command do
argument :hello
run context do
IO.inspect(context)
end
end
end