Error: no method matching iterate(::Expr) - macros

macro superM(arg, argc)
map(foo,argc)
end
#superM test {1,2}
I just want to passe the argument argc to the map function, but I'm getting this error: no method matching iterate(::Expr)

the argc is an Expression whose head is braces {}:
julia> dump(:({1, 2}))
Expr
head: Symbol braces
args: Array{Any}((2,))
1: Int64 1
2: Int64 2
so what you really want is the args field of the expression {1,2}
julia> macro superM(arg, argc)
map(identity,argc.args)
end
#superM (macro with 1 method)
julia> #superM test {1,2}
2-element Vector{Int64}:
1
2

Related

Invalid assignment error inside Julia macro

I was following along this notebook (originally written in Julia 0.x, I am using Julia 1.7.1). One of the cells defines the following macro.
macro twice(ex)
quote
$ex
$ex
end
end
On the very next cell, this macro is invoked.
x = 0
#twice println(x += 1)
Replicating this in the REPL (for brevity) results in the following error.
ERROR: syntax: invalid assignment location "Main.x" around REPL[1]:3
Stacktrace:
[1] top-level scope
# REPL[4]:1
So, I understand that x += 1 is somehow causing this problem, but after going through the docs (metaprogramming), I could not figure out why exactly this is an invalid assignment or how to fix this.
#macroexpand #twice println(x += 1) successfully returns the following.
quote
#= REPL[1]:3 =#
Main.println(Main.x += 1)
#= REPL[1]:4 =#
Main.println(Main.x += 1)
end
So, I tried evaling this in the top-level without the Main.s, and it evaluates successfully.
x = 0
eval(quote
println(x += 1)
println(x += 1)
end)
Output:
1
2
But, if I add the module name explicitly, it throws a different error.
eval(quote
Main.println(Main.x += 1)
Main.println(Main.x += 1)
end)
ERROR: cannot assign variables in other modules
Stacktrace:
[1] setproperty!(x::Module, f::Symbol, v::Int64)
# Base ./Base.jl:36
[2] top-level scope
# REPL[14]:3
[3] eval
# ./boot.jl:373 [inlined]
[4] eval(x::Expr)
# Base.MainInclude ./client.jl:453
[5] top-level scope
# REPL[14]:1
I tried a few other things, but these are the only things that I think might be getting somewhere.
Why exactly is the assignment in the first macro code block invalid? Is it for the same reason that evaling in the top-level with the module Main specified fails?
How can this invalid assignment be circumvented, OR how do I port this to Julia 1.7?
Use esc (This "prevents the macro hygiene pass from turning embedded variables into gensym variables"):
julia> macro twice(ex)
esc(quote
$ex
$ex
end)
end;
julia> x=1
1
julia> #twice println(x += 1)
2
3

is it possible to execute a code inside an expression?

I have this macro:
macro superM(name, field)
:(struct $(esc(name))
map(x -> (for i = 1:length(x) return $(esc(x[i][1])) end),field.args)
end)
end
#superM test ((arg1,2),(arg2,4))
I just want to map the second argument to get the arguments for the struct in the macro.
So, this macro should define a struct like this:
struct test
arg1
arg2
end
As I understand that I'm writing the map function inside an expression, that means my code will not execute and the map function will be a part of my struct. But is there any way to execute the map function inside the expression?
Here I got error: LoadError: UndefVarError: x not defined
You could try:
macro superM(name, field)
code = Base.remove_linenums!(quote
struct $name
end
end)
append!(code.args[1].args[3].args,[f.args[1] for f in field.args])
code
end
Now give it a spin:
julia> #macroexpand #superM(mysss,((field1,1),(field2,2)))
quote
struct mysss
field1
field2
end
end
julia> #superM(mysss,((field1,1),(field2,2)))
julia> mysss(1,5)
mysss(1, 5)
julia> dump(mysss(1,"ala"))
mysss
field1: Int64 1
field2: String "ala"

Julia: Macros, Expressions and Meta.parse

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.

julia metaprogramming and nloops variable evaluation

I am a noob at metaprogramming so maybe I am not understanding this. I thought the purpose of the #nloops macro in Base.Cartesian was to make it possible to code an arbitrary number of nested for loops, in circumstances where the dimension is unknown a priori. In the documentation for the module, the following example is given:
#nloops 3 i A begin
s += #nref 3 A i
end
which evaluates to
for i_3 = 1:size(A,3)
for i_2 = 1:size(A,2)
for i_1 = 1:size(A,1)
s += A[i_1,i_2,i_3]
end
end
end
Here, the number 3 is known a priori. For my purposes, however, and for the purposes that I thought nloops was created, the number of nested levels is not known ahead of time. So I would not be able to hard code the integer 3. Even in the documentation, it is stated:
The (basic) syntax of #nloops is as follows:
The first argument must be an integer (not a variable) specifying the number of loops.
...
If I assign an integer value - say the dimension of an array that is passed to a function - to some variable, the nloops macro no longer works:
b = 3
#nloops b i A begin
s += #nref b A i
end
This returns an error:
ERROR: LoadError: MethodError: no method matching _nloops(::Symbol, ::Symbol, ::Symbol, ::Expr)
Closest candidates are:
_nloops(::Int64, ::Symbol, ::Symbol, ::Expr...) at cartesian.jl:43
...
I don't know how to have nloops evaluate the b variable as an integer rather than a symbol. I have looked at the documentation and tried various iterations of eval and other functions and macros but it is either interpreted as a symbol or an Expr. What is the correct, julian way to write this?
See supplying the number of expressions:
julia> A = rand(4, 4, 3) # 3D array (Array{Int, 3})
A generated function is kinda like a macro, in that the resulting expression is not returned, but compiled and executed on invocation/call, it also sees the type (and their type parameters of course) of the arguments, ie:
inside the generated function, A is Array{T, N}, not the value of the array.
so T is Int and N is 3!
Here inside the quoted expression, N is interpolated into the expression, with the syntax $N, which evaluates to 3:
julia> #generated function mysum(A::Array{T,N}) where {T,N}
quote
s = zero(T)
#nloops $N i A begin
s += #nref $N A i
end
s
end
end
mysum (generic function with 1 method)
julia> mysum(A)
23.2791638775186
You could construct the expression and then evaluate it, ie.:
julia> s = 0; n = 3;
julia> _3loops = quote
#nloops $n i A begin
global s += #nref $n A i
end
end
quote
#nloops 3 i A begin
global s += #nref(3, A, i)
end
end
julia> eval(_3loops)
julia> s
23.2791638775186
I have scrubbed manually the LineNumberNodes from the AST for readability (there is also MacroTools.prettify, that does it for you).
Running this example in the REPL needs to declare s as global inside the loop in Julia 1.0.

How can I define a julia macro that defines a macro?

I would have thought this would work:
macro meta_meta(x,y)
:(macro $x(arg) :($($y) + $arg) end)
end
The expected behavior is that calling #meta_meta(f,2) should be equivalent to macro f(arg) :(2 + $arg) end
In other words:
julia> #meta_meta(f,2)
julia> #f(3)
5
Instead I get:
ERROR: syntax: invalid macro definition
I'm at a bit of a loss for how to proceed. I see that the expression tree for this macro is different from the one I get if I manually generate #f and examine its expression tree, and I've tried several iterations of #meta_meta, but I cannot figure out how to change my definition to get it working.
Macro hygiene is a little finnicky when dealing with a quote inside a quote. Often I find the only way is to refuse macro hygiene entirely, and use gensym liberally to simulate it.
However in your reduced example, it's straightforward to just turn the inner quote into an Expr:
julia> macro meta_meta(x, y)
:(macro $(esc(x))(arg) Expr(:call, :+, $(esc(y)), esc(arg)) end)
end
#meta_meta (macro with 1 method)
julia> #meta_meta f 2
#f (macro with 1 method)
julia> #f 3
5
If things get more complicated, the approach I mentioned above involves turning off macro hygiene with esc. This means that we have to do the hygiene ourself, hence the gensym:
julia> macro meta_meta(x, y)
arg = gensym()
esc(:(macro $x($arg) :($$y + $$arg) end))
end
#meta_meta (macro with 1 method)
julia> #meta_meta f 2
#f (macro with 1 method)
julia> #f 3
5