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.
Related
I have been messing around with generated functions in Julia, and have come to a weird problem I do not understand fully: My final goal would involve calling a macro (more specifically #tullio) from within a generated function (to perform some tensor contractions that depend on the input tensors). But I have been having problems, which I narrowed down to calling the macro from within the generated function.
To illustrate the problem, let's consider a very simple example that also fails:
macro my_add(a,b)
return :($a + $b)
end
function add_one_expr(x::T) where T
y = one(T)
return :( #my_add($x,$y) )
end
#generated function add_one_gen(x::T) where T
y = one(T)
return :( #my_add($x,$y) )
end
With these declarations, I find that eval(add_one_expr(2.0)) works just as expected and returns and expression
:(#my_add 2.0 1.0)
which correctly evaluates to 3.0.
However evaluating add_one_gen(2.0) returns the following error:
MethodError: no method matching +(::Type{Float64}, ::Float64)
Doing some research, I have found that #generated actually produces two codes, and in one only the types of the variables can be used. I think this is what is happening here, but I do not understand what is happening at all. It must be some weird interaction between macros and generated functions.
Can someone explain and/or propose a solution? Thank you!
I find it helpful to think of generated functions as having two components: the body and any generated code (the stuff inside a quote..end). The body is evaluated at compile time, and doesn't "know" the values, only the types. So for a generated function taking x::T as an argument, any references to x in the body will actually point to the type T. This can be very confusing. To make things clearer, I recommend the body only refer to types, never to values.
Here's a little example:
julia> #generated function show_val_and_type(x::T) where {T}
quote
println("x is ", x)
println("\$x is ", $x)
println("T is ", T)
println("\$T is ", $T)
end
end
show_val_and_type
julia> show_val_and_type(3)
x is 3
$x is Int64
T is Int64
$T is Int64
The interpolated $x means "take the x from the body (which refers to T) and splice it in.
If you follow the approach of never referring to values in the body, you can test generated functions by removing the #generated, like this:
julia> function add_one_gen(x::T) where T
y = one(T)
quote
#my_add(x,$y)
end
end
add_one_gen
julia> add_one_gen(3)
quote
#= REPL[42]:4 =#
#= REPL[42]:4 =# #my_add x 1
end
That looks reasonable, but when we test it we get
julia> add_one_gen(3)
ERROR: UndefVarError: x not defined
Stacktrace:
[1] macro expansion
# ./REPL[48]:4 [inlined]
[2] add_one_gen(x::Int64)
# Main ./REPL[48]:1
[3] top-level scope
# REPL[49]:1
So let's see what the macro gives us
julia> #macroexpand #my_add x 1
:(Main.x + 1)
It's pointing to Main.x, which doesn't exist. The macro is being too eager, and we need to delay its evaluation. The standard way to do this is with esc. So finally, this works:
julia> macro my_add(a,b)
return :($(esc(a)) + $(esc(b)))
end
#my_add
julia> #generated function add_one_gen(x::T) where T
y = one(T)
quote
#my_add(x,$y)
end
end
add_one_gen
julia> add_one_gen(3)
4
All these following lines of code are Julia expressions:
x = 10
1 + 1
println("hi")
if you want to pass an expression to a macro, it works like this. Macro foo just returns the given expression, which will be executed:
macro foo(ex)
return ex
end
#foo println("yes") # prints yes
x = #foo 1+1
println(x) # prints 2
If you want to convert a string into an expression, you can use Meta.parse():
string = "1+1"
expr = Meta.parse(string)
x = #foo expr
println(x) # prints 1 + 1
But, obviously, the macro treats expr as a symbol. What am i getting wrong here?
Thanks in advance!
Macro hygiene is important "macros must ensure that the variables they introduce in their returned expressions do not accidentally clash with existing variables in the surrounding code they expand into." There is a section in the docs. It is easiest just to show a simple case:
macro foo(x)
return :($x)
end
When you enter an ordinary expression in the REPL, it is evaluated immediately. To suppress that evaluation, surround the expression with :( ).
julia> 1 + 1
2
julia> :(1 + 1)
:(1 + 1)
# note this is the same result as you get using Meta.parse
julia> Meta.parse("1 + 1")
:(1 + 1)
So, Meta.parse will convert an appropriate string to an expression. And if you eval the result, the expression will be evaluated. Note that printing a simple expression removes the outer :( )
julia> expr = Meta.parse("1 + 1")
:(1 + 1)
julia> print(expr)
1 + 1
julia> result = eval(expr)
2
Usually, macros are used to manipulate things before the usual evaluation of expressions; they are syntax transformations, mostly. Macros are performed before other source code is compiled/evaluated/executed.
Rather than seeking a macro that evaluates a string as if it were typed directly into the REPL (without quotes), use this function instead.
evalstr(x::AbstractString) = eval(Meta.parse(x))
While I do not recommend this next macro, it is good to know the technique.
A macro named <name>_str is used like this <name>"<string contents>" :
julia> macro eval_str(x)
:(eval(Meta.parse($x)))
end
julia> eval"1 + 1"
2
(p.s. do not reuse Base function names as variable names, use str not string)
Please let me know if there is something I have not addressed.
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
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
This question builds off of a previous SO question which was for building expressions from expressions inside of of a macro. However, things got a little trucker when quoting the whole expression. For example, I want to build the expression :(name=val). The following:
macro quotetest(name,val)
quote
nm = Meta.quot($(QuoteNode(name)))
v = Meta.quot($(QuoteNode(val)))
println(nm); println(typeof(nm))
println(v); println(typeof(val))
end
end
#quotetest x 5 # Test case: build :(x=5)
prints out
:x
Expr
$(Expr(:quote, 5))
Expr
showing that I am on the right path: nm and val are the expressions that I want inside of the quote. However, I can't seem to apply the previous solution at this point. For example,
macro quotetest(name,val)
quote
nm = Meta.quot($(QuoteNode(name)))
v = Meta.quot($(QuoteNode(val)))
println(nm); println(typeof(nm))
println(v); println(typeof(v))
println(:($(Expr(:(=),$(QuoteNode(nm)),$(QuoteNode(val))))))
end
end
fails, saying nm is not defined. I tried just interpolating without the QuoteNode, escaping the interpolation $(esc(nm)), etc. I can't seem to find out how to make it build the expression.
I think you are using $ signs more than you need to. Is this what you're looking for?
julia> macro quotetest(name,val)
quote
expr = :($$(QuoteNode(name)) = $$(QuoteNode(val)))
println(expr)
display(expr)
println(typeof(expr))
end
end
#quotetest (macro with 1 method)
julia> #quotetest test 1
test = 1
:(test = 1)
Expr