Julia: How do I create a macro that returns its argument? - macros

My question is quite similar to this one, but with a difference. I want to create a macro (or whatever) that behaves this way:
julia> #my-macro x + 2
:(x + 2)
(note that x + 2 is not enclosed in quotes). Is there something like that in Julia? And if there is not, how do I do it? (Please, give a detailed explanation about why it works.)

The input expression to the macro needs to be quoted because a macro returns an expression, which are evaluated, while you would like to get the expression itself, hence you need an extra quoting. The quoting can be done as:
macro mymacro(ex)
Expr(:quote,ex) # this creates an expression that looks like :(:(x + 2))
end
e=#mymacro x + 2 #returns :(x + 2)

Another shorter possibility:
macro mymacro(ex)
QuoteNode(ex)
end
e = #mymacro x + 2 #returns :(x + 2)

Related

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.

Quote-unquote idiom in Julia & concatenating Expr objects

I'd like to write a simple macro that shows the names & values of variables. In Common Lisp it would be
(defmacro dprint (&rest vars)
`(progn
,#(loop for v in vars
collect `(format t "~a: ~a~%" ',v ,v))))
In Julia I had two problems writing this:
How can I collect the generated Expr objects into a block? (In Lisp, this is done by splicing the list with ,# into progn.) The best I could come up with is to create an Expr(:block), and set its args to the list, but this is far from elegant.
I need to use both the name and the value of the variable. Interpolation inside strings and quoted expressions both use $, which complicates the issue, but even if I use string for concatenation, I can 't print the variable's name - at least :($v) does not do the same as ',v in CL...
My current macro looks like this:
macro dprint(vars...)
ex = Expr(:block)
ex.args = [:(println(string(:($v), " = ", $v))) for v in vars]
ex
end
Looking at a macroexpansion shows the problem:
julia> macroexpand(:(#dprint x y))
quote
println(string(v," = ",x))
println(string(v," = ",y))
end
I would like to get
quote
println(string(:x," = ",x))
println(string(:y," = ",y))
end
Any hints?
EDIT: Combining the answers, the solution seems to be the following:
macro dprint(vars...)
quote
$([:(println(string($(Meta.quot(v)), " = ", $v))) for v in vars]...)
end
end
... i.e., using $(Meta.quot(v)) to the effect of ',v, and $(expr...) for ,#expr. Thank you again!
the #show macro already exists for this. It is helpful to be able to implement it yourself, so later you can do other likes like make one that will show the size of an Array..
For your particular variant:
Answer is Meta.quot,
macro dprint(vars...)
ex = Expr(:block)
ex.args = [:(println($(Meta.quot(v)), " = ", $v)) for v in vars]
ex
end
See with:
julia> a=2; b=3;
julia> #dprint a
a = 2
julia> #dprint a b
a = 2
b = 3
oxinabox's answer is good, but I should mention the equivalent to ,#x is $(x...) (this is the other part of your question).
For instance, consider the macro
macro _begin(); esc(:begin); end
macro #_begin()(args...)
quote
$(args...)
end |> esc
end
and invocation
#begin x=1 y=2 x*y
which (though dubiously readable) produces the expected result 2. (The #_begin macro is not part of the example; it is required however because begin is a reserved word, so one needs a macro to access the symbol directly.)
Note
julia> macroexpand(:(#begin 1 2 3))
quote # REPL[1], line 5:
1
2
3
end
I consider this more readable, personally, than pushing to the .args array.

Interpolating an expression into an expression inside of a quote

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

CoffeeScript re-arranges method call parenthesis, why?

I have written this in CoffeeScript:
expect (#controllerInstance[fn]).toHaveBeenCalled()
and it's been compiled to this:
return expect(this.controllerInstance[fn].toHaveBeenCalled());
Why has it re-arranged the method call parenthesis? And how would I make it compile to what I want?
what I need to see is:
expect(this.controllerInstance[fn]).toHaveBeenCalled()
Parentheses serve two purposes in CoffeeScript:
Expression grouping, e.g. (6 + 11) * 23 or f (-> 6), (-> 11).
Function calling, e.g. f(), g('pancakes').
Since parentheses are sometimes optional in a function call, there is some ambiguity in:
f (expr)
Are those parentheses being used to call f with expr as its argument or are the parentheses really a part of f's argument? CoffeeScript chooses the latter interpretation.
You'll see similar problems if write:
f (x) + 1
CoffeeScript sees that as:
f((x) + 1)
Similarly, if you write:
f (x, y)
you'll get an unexpected , error; CoffeeScript doesn't have a comma operator so x, y is not a valid expression.
You can remove the ambiguity by removing the whitespace before the opening parenthesis:
expect(#controllerInstance[fn]).toHaveBeenCalled()
Removing the space after expect forces CoffeeScript to view the parentheses around #controllerInstance[fn] to be seen as function-calling parentheses.

Brackets needed when using infix approach?

Really simple Scala question.
How come the infix approach to 1 + 2 does not need brackets?
scala>1 + 2
res7: Int = 3
But the dot approach does?
scala>1 .+(2)
res8: Int = 3
scala> 1 .+2
<console>:1: error: ';' expected but integer literal found.
1 .+2
^
Everything in Scala is an object so 1 .+(2) means to call method + on object 1 with parameter 2. And of course if you call a method like this, you need to enclose parameters in brackets, just like in regular obj.somemethod(someparam,foo,bar).
Infix notation (1 + 2) actually means the same thing (it is syntactic sugar to call method with one parameter).
And the space before dot is needed so that dot is interpreted as method invocation and not as decimal separator. Otherwise 1.+(2) or 1.+2 will be interpreted as 1.0 + 2.
I think it's to do with language definition:
A left-associative binary operation e1 op e2 is interpreted as e1.op(e2).
http://www.scala-lang.org/docu/files/ScalaReference.pdf
The form 1 .+ 2 is not specified anywhere so my guess is that the compiler is looking for either 1 + 2 or 1.+(2). In fact the compiler converts 1+2 into 1.+(2) normally. When using the . it expects a function and not the infix syntax.
Bottom line: you can use either but not something half way there.
PD: someone commented that calling a method you need to use it like this: obj.somemethod(someparam,foo,bar) but on that case you can also do this: obj somemethod (someparam,foo,bar) and you have to leave the spaces for it to work.