Elixir Macro issue with unquoting - macros

Can you please have a look at my Macro?
I am getting undefined function number/0 error, and I can't figure it out why.
defmodule DbUtil do
defmacro __using__(opts) do
quote do
import unquote(__MODULE__)
#before_compile unquote(__MODULE__)
end
end
defmacro __before_compile__(%{module: definition} = _env) do
quote do
import Ecto.Query
def last do
from x in unquote(definition), order_by: [desc: x.id], limit: 1
end
# This dumps error
def limits(number) do
from a in unquote(definition), limit: ^unquote(number)
end
end
end
end

You don't need to unquote number. unquote is used when you want to inject a variable present outside the quote block. Since number is defined inside the quote, you don't need to unquote. The following should work for you:
def limits(number) do
from a in unquote(definition), limit: ^number
end

Related

Can I expand a macro inside a quote of another macro in Elixir?

Given the below code, the line use Composite, user_opts: user_opts ends up as [{:user_opts, [line: 3, counter: {MockUserNode1, 2}], Automaton.Node}] inside the using(opts) as opts. Is there any way to inject that code inside the outer macro?
defmacro __using__(user_opts) do
a =
if Enum.member?(Composite.types(), user_opts[:node_type]) do
IO.inspect(user_opts)
quote bind_quoted: [user_opts: user_opts] do
use DynamicSupervisor
use Composite, user_opts: user_opts
end
else
quote do: use(Action)
end
end
Answering the question stated: it’s perfectly possible to call macros from inside other macros, the just inject the AST recursively in the end.
defmodule DeeplyUsed do
defmacro __using__(opts) do
quote bind_quoted: [opts: opts] do
opts
end
end
end
defmodule Used do
defmacro __using__(opts) do
quote bind_quoted: [opts: opts] do
use DeeplyUsed, opts: opts
end
end
end
defmodule Using do
use Used, line: 3, counter: {MockUserNode1, 2}
end
That said, your issue is induced.
Sidenote: [{:user_opts, _, Automaton.Node}] looks indeed very suspicious there, that’s not how keyword lists are being quoted. Start with explicit unquoting and logging what comes to user_opts there.
defmacro __using__(user_opts) do
IO.inspect(user_opts, label: "Outside")
quote do
IO.inspect(unquote(user_opts), label: "Inside")
use Composite, user_opts: unquote(user_opts)
end
end

Why does one of these semingly equivalent macros fail?

Consider these two macro definitions:
macro createTest1()
quote
function test(a = false)
a
end
end |> esc
end
macro createTest2()
args = :(a = false)
quote
function test($args)
a
end
end |> esc
end
According to the builtin Julia facilities they should both evaluate to the same thing when expanded:
println(#macroexpand #createTest1)
begin
function test(a=false)
a
end
end
println(#macroexpand #createTest2)
begin
function test(a = false)
a
end
end
Still I get a parse error when trying to evaluate the second macro:
#createTest2
ERROR: LoadError: syntax: "a = false" is not a valid function argument name
It is a space in the second argument list. However, that should be correct Julia syntax. My guess is that it interprets the second argument list as another Julia construct compared to the first. If that is the case how do I get around it?
The reason that the second macro is failing as stated in my question above. It looks correct when printed however args is not defined correctly and Julia interprets it as an expression which is not allowed. The solution is to instead define args according to the rules for function parameters. The following code executes as expected:
macro createTest2()
args = Expr(:kw, :x, false)
quote
function test($(args))
a
end
end |> esc
end

bind_quoted does not create a binding inside a def

bind_quoted doesn't seem to work for me. Here is an example that doesn't use bind_quoted, and it works as expected:
defmodule Animals do
defmacro dog do
x = 4
quote do
def go do
IO.puts unquote(x)
end
end
end
end
defmodule Test do
require Animals
Animals.dog #inject the definition of go() into the Test module
end
In iex:
iex(10)> c "a.exs"
warning: redefining module Animals (current version defined in memory)
a.exs:1
warning: redefining module Test (current version defined in memory)
a.exs:15
[Test, Animals]
iex(11)> Test.go
4
:ok
iex(12)>
But the bind_quoted docs say:
...the :bind_quoted option is recommended every time one desires to
inject a value into the quote.
Okay, let's be conformant:
defmodule Animals do
defmacro dog do
x = 4
quote bind_quoted: [x: x] do
def go do
IO.puts x
end
end
end
end
defmodule Test do
require Animals
Animals.dog #inject go() into the Test module
end
Compiling in iex:
iex(10)> c "a.exs"
warning: redefining module Animals (current version defined in memory)
a.exs:1
warning: redefining module Test (current version defined in memory)
a.exs:15
warning: variable "x" does not exist and is being expanded to "x()", please use parentheses to remove the ambiguity or change the variable name
a.exs:17
== Compilation error in file a.exs ==
** (CompileError) a.exs:17: undefined function x/0
(stdlib) lists.erl:1338: :lists.foreach/2
(stdlib) erl_eval.erl:670: :erl_eval.do_apply/6
** (CompileError) compile error
(iex) lib/iex/helpers.ex:183: IEx.Helpers.c/2
iex(10)>
The relevant message in the error report is:
warning: variable "x" does not exist
Why not?
Normally, yes. That's how it would work. But the def call itself is a macro, so you would still need to use unquote inside it. If you directly quoted IO.puts, it would have worked without issues.
Here's a slightly modified version of your code demonstrating it:
defmodule Animals do
defmacro dog do
x = 4
quote(bind_quoted: [xx: x]) do
IO.puts(xx)
end
end
end
defmodule Test do
require Animals
def go do
Animals.dog
end
end
Now back to your implementation; I bound x to xx in this example to explicitly show you that if you try to unquote x here (instead of xx) it will throw a compilation error:
defmodule Animals do
defmacro dog do
x = 4
quote(bind_quoted: [xx: x]) do
def go do
IO.puts(unquote(xx))
end
end
end
end

Elixir Macros: Get unquoted parameter value outside of quote

I have a macro which gets a module name as parameter and I want to call a function on that module to get some data in order to generate the quote block.
Example:
defmacro my_macro(module) do
data = apply(module, :config, [])
# do something with data to generate the quote do end
end
Obviously, this doesn't work because the parameter value is quoted. I could fetch the data inside the quote block and act accordingly but that would put the whole logic inside the module that uses my macro which is quite dirty. I want to inject as little code as possible.
You can extract the module out by pattern matching with its quoted form: {:__aliases__, _, list} where list is a list of atoms which when concatenated with a dot (use Module.concat/1) produces the full module name.
defmodule A do
defmacro my_macro({:__aliases__, _, list}) do
module = Module.concat(list)
module.foo()
end
end
defmodule B do
def foo do
quote do
42
end
end
end
defmodule C do
import A
IO.inspect my_macro B
end
Output:
42

Double unquoting for dynamically generated macros

Given the following:
for fn_name <- [:foo, :bar, :baz] do
defmacro unquote(fn_name)(do: inner) do
fn_name = unquote(fn_name) # <--- Why?
quote do
IO.puts "#{unquote(fn_name)} called"
unquote(inner)
end
end
end
What's the reason for fn_name = unquote(fn_name)? If I omit this line, it's a compile error. What's the reason for this "double" unquoting?
Let's simplify the example a little bit:
for fn_name <- [:foo, :bar, :baz] do
defmacro unquote(fn_name)(do: inner) do
fn_name = unquote(fn_name) # <--- Why?
quote do
{unquote(fn_name), unquote(inner)}
end
end
end
In the example above, because quote is returning a tuple with two unquoted elements, it is equivalent to:
for fn_name <- [:foo, :bar, :baz] do
defmacro unquote(fn_name)(do: inner) do
fn_name = unquote(fn_name) # <--- Why?
{fn_name, inner}
end
end
Now it is easier to understand what happens if you don't unquote(fn_name) before: the variable fn_name simply wouldn't exist inside the macro definition. Remember that all defs (def, defp, defmacro, etc) start a new variable scope, so if you want to use fn_name inside, you need to define it somehow.
The other property we are seeing in this code is that Elixir will stop unquoting when it sees a quote. So in the quote above, unquote won't be unquoted when the macro is defined but rather when the macro is executed, which also explains why the variable is required to be defined inside the macro.
It's because of Hygiene.
Elixir has the concept of macro hygiene. Hygiene means that variables, imports, and aliases that you define in a macro do not leak into the caller’s own definitions.
for fn_name <- [:foo, :bar, :baz] do
defmacro unquote(fn_name)(do: inner) do
fn_name = unquote(fn_name) # <-- This is macro's context
quote do
IO.puts "#{unquote(fn_name)} called" # <-- This is caller's context
unquote(inner)
end
end
end
You should read Hygiene Protects the Caller’s Context from Chris McCord's Metaprogramming Elixir book