bind_quoted does not create a binding inside a def - macros

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

Related

Why the dynamic call results in “undefined function”?

I have a module that dynamically routes external calls to it’s own functions in the following way:
defmodule A do
defmacro call(name) do
quote do
fun = & unquote(:"A.#{name}")(&1)
fun.(:foo)
end
end
def test(param), do: IO.inspect(param, label: "test")
end
#⇒ {:module, A, ..., {:test, 1}}
The module was successfully compiled and A.test/1 is there.
A.test :foo
#⇒ test: :foo
Now I try to call it as:
defmodule B do
require A
def test, do: A.call(:test)
end
#⇒ ** (CompileError) iex:21: undefined function A.test/1
# (stdlib) lists.erl:1338: :lists.foreach/2
# (stdlib) erl_eval.erl:677: :erl_eval.do_apply/6
# (iex) lib/iex/evaluator.ex:249: IEx.Evaluator.handle_eval/5
What is wrong with this dynamic call dispatch and why the error message contradicts the reality?
The error message is misleading. & unquote(:"A.#{name}")(&1) will call a function literally named A.test in the current scope, not the test/1 function of module A:
defmodule A do
defmacro call(name) do
quote do
fun = & unquote(:"A.#{name}")(&1)
fun.(:foo)
end
end
def unquote(:"A.test")(param), do: IO.inspect(param, label: "!!!")
end
defmodule B do
require A
import A
def test, do: A.call(:test)
end
B.test
Output:
!!!: :foo
To make it call the test/1 function of module A, you can do & A.unquote(:"#{name}")(&1):
defmodule A do
defmacro call(name) do
quote do
fun = & A.unquote(:"#{name}")(&1)
fun.(:foo)
end
end
def test(param), do: IO.inspect(param, label: "test")
end
defmodule B do
require A
def test, do: A.call(:test)
end
B.test
Output:
test: :foo

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

Elixir Macro issue with unquoting

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

Why is the error on the wrong line?

I am diving into Metaprogramming Elixir by Chris McCord.
I made a spelling mistake while typing one of examples:
defmodule Math do
defmacro say({:+, _, [lhs, rhs]}) do
qoute do #spelling mistake (swapped "o" and "u")
lhs = unquote(lhs) #(CompileError) math.exs:4: undefined function lhs/0
rhs = unquote(rhs)
result = lhs + rhs
IO.puts "#{lhs} plus #{rhs} is #{result}"
result
end
end
defmacro say({:*, _, [lhs, rhs]}) do
qoute do #spelling mistake copied
lhs = unquote(lhs)
rhs = unquote(rhs)
result = lhs * rhs
IO.puts "#{lhs} times #{rhs} is #{result}"
result
end
end
end
In the shell, the errors are meaningful:
iex(1)> qoute do: 1 + 2 #spelling mistake
** (RuntimeError) undefined function: qoute/1
iex(1)> unquote do: 1
** (CompileError) iex:1: unquote called outside quote
Why compiling this file gives error in the next line? Is my spelling mistake some valid construct?
The error appears in correct file, if I remove the unquote.
defmodule Math do
defmacro say({:+, _, [lhs, rhs]}) do
qoute do #function qoute/1 undefined
result = lhs + rhs
IO.puts "#{lhs} plus #{rhs} is #{result}"
result
end
end
...
Why using unquote moved the error somewhere else?
That's because once you call qoute/1, Elixir assumes it is a function that will be defined later, and proceeds to compile the code as a function call. However, when we try to compile it, we see an unquote, assume there is a variable defined outside, and everything crashes when it doesn't.
There is no way we can work around it because the error happens when we are expanding code and is exactly when quote/unquote are expanded too.

How can I refer to a module variable in a function without referring to its module in Elixir 1.0.3? In its parent scope?

I'd like to make a function in Elixir 1.0.3 refer to a variable inside its "parent" scope. In this case, its parent scope is a module.
Here's the same code as that which I used in my last question:
defmodule Rec do
def msgurr(text, n) when n <= 1 do
IO.puts text
end
def msgurr(text, n) do
IO.puts text
msgurr(text, n - 1)
end
end
If I change it to the following:
defmodule Rec do
counter = "done!"
def msgurr(text, n) when n <= 1 do
IO.puts text
IO.puts Rec.counter
end
def msgurr(text, n) do
IO.puts text
msgurr(text, n - 1)
end
end
It compiles just fine, but I get the following error if I try the msgurr function:
** (UndefinedFunctionError) undefined function: Rec.counter/0
Rec.counter()
recursion_and_import_test.exs:5: Rec.msgurr/2
I also tried the following:
defmodule Rec do
counter = "done!"
def msgurr(text, n) when n <= 1 do
import Rec
IO.puts text
IO.puts Rec.counter
end
def msgurr(text, n) do
IO.puts text
msgurr(text, n - 1)
end
end
I get a compile-time warnings here, though:
➜ ubuntu elixirc recursiontest.exs
recursion_and_import_test.exs:1: warning: redefining module Rec
recursion_and_import_test.exs:2: warning: variable counter is unused
recursion_and_import_test.exs:4: warning: unused import Rec
When I attempt to use the msgurr function:
➜ ubuntu iex
Erlang/OTP 17 [erts-6.3] [source] [64-bit] [smp:8:8] [async-threads:10] [kernel-poll:false]
Interactive Elixir (1.0.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> import Rec
nil
iex(2)> Rec.msgurr("blah", 3)
blah
blah
blah
** (UndefinedFunctionError) undefined function: Rec.counter/0
Rec.counter()
recursiontest.exs:6: Rec.msgurr/2
I seem to be unable to import a variable of my own from a module into a function inside that module.
I've gone over the import documentation, but I can't seem to make much sense from it of how to do this sort of thing. Should I check the Erlang docs?
You are confusing Modules with Objects.
Rec.counter
Always refers to the function inside the Rec Module. That's what the error messages are telling you, they can't find the function. Modules can't have variables in the way you are thinking of them.
Modules can have attributes. While it might be possible to fudge want you want
with a module attribute, you should just make a function that returns a constant if you want to reference it using Rec.counter.
def counter do
"done!"
end
There's more on module attributes here, but if you want to be able to think in elixir, you need to start thinking "functions not variables".