Can someone resolve this macro error I'm having, it only started happening in version 0.6:
mutable struct Foo
x::Int
end
macro test(myfoo)
quoteblock =
quote
myfoo.x += 1
end
return quoteblock
end
function func(myfoo)
#test myfoo
println(myfoo.x)
end
foo = Foo(3)
func(foo)
In theory this should just replace the line #test myfoo in the function func with myfoo.x += 1 at compile time, which should work, but instead I get the error:
UndefVarError: myfoo not defined
The corresponding change-notes are listed here:
When a macro is called in the module in which that macro is defined,
global variables in the macro are now correctly resolved in the macro
definition environment. Breakage from this change commonly manifests
as undefined variable errors that do not occur under 0.5. Fixing such
breakage typically requires sprinkling additional escs in the
offending macro (#15850).
so the answer is to escape myfoo:
macro test(myfoo)
quote
$(esc(myfoo)).x += 1
end
end
Related
Recently started to try and learn Julia through examples. I am basically trying to figure out how to access a struct property from within a function inside the struct itself. E.g:
struct Test
a::Int
foo::Function
function Test()
return new(777, xfoo)
end
function xfoo()
println(a)
end
end
t = Test()
t.foo()
I get:
ERROR: LoadError: UndefVarError: a not defined
Stacktrace:
[1] (::var"#xfoo#1")()
# Main /tmp/j.jl:10
[2] top-level scope
# /tmp/j.jl:15
in expression starting at /tmp/j.jl:15
Am I using Julia wrong or am I missing something?
Julia is not object oriented language so object oriented patterns are usually not a good idea.
Hence xfoo should be outside of Test:
function xfoo(t::Test)
println(t.a)
end
There are packages that try to emulate OOP with Julia (however this is not a Julian pattern): https://github.com/Suzhou-Tongyuan/ObjectOriented.jl
You can also easily find quite a lot of discussion behind the design decision no to make Julia OOP. Start with: https://discourse.julialang.org/t/why-there-is-no-oop-object-oriented-programming-in-julia/86723
Workaround
Just out of curiosity one can find some workaround to attach a function to a struct (not a recommended design pattern!). For an example:
mutable struct MyTest
a::Int
foo::Function
function MyTest()
s = Ref{MyTest}()
s[] = new(777, () -> println(s[].a))
s[]
end
end
And some sample usage:
julia> t = MyTest();
julia> t.foo()
777
julia> t.a = 900;
julia> t.foo()
900
Some of the parameters to a simulation I am writing are functions. When the output is generated, I want to put the definition of these functional parameters in the output. I have in mind a macro that somehow saves the definition as a string, and then defines it. For example, here's what I do now:
borda_score_fn(p) = exp(1/p)
global g_borda_score_fn_string = "exp(1/p)"
And then I write g_borda_score_fn_string to my output. But this is really ugly!
What I would like to do is something like this:
#paramfn borda_score_fn(p) = exp(1/p)
And later be able to both call borda_score_fn(p), and have the form (i.e., "exp(1/p)") available for writing to my output log. (The string form might get stashed in a global dict, actually, they both could.)
I have tried many version of this, but can't get the right set of parses and calls to get it to work. Any help would be appreciated.
This may be a bit different than what you have in mind, but one perhaps "Julian" approach might be to have the function itself return the form string via multiple dispatch, rather than defining a whole new global variable just for that. For example, say we have a type
struct Form end
that we can use for dispatch, then we can write
borda_score_fn(p) = exp(1/p)
borda_score_fn(::Form) = "exp(1/p)"
which can then be retrieved just by calling the function with our type
julia> borda_score_fn(2)
1.6487212707001282
julia> borda_score_fn(Form())
"exp(1/p)"
That might actually be not bad on its own. But, if you want a macro to do both parts at once, then something along the lines of
macro paramfn(e)
name = esc(e.args[1].args[1])
str = string(e.args[2].args[2])
f = esc(e)
quote
$name(::Form) = $str
$f
end
end
would let you write
julia> #paramfn borda_score_fn(p) = exp(1/p)
borda_score_fn (generic function with 2 methods)
julia> borda_score_fn(1)
2.718281828459045
julia> borda_score_fn(Form())
"exp(1 / p)"
For completeness, here's how you can do it in a way more similar to your original approach, but more idiomatically than with a global variable:
julia> module FormOf
export formof, #paramfn
function formof end
macro paramfn(expr)
name = esc(expr.args[1].args[1])
form_str = string(expr.args[2].args[2])
quote
$(esc(expr))
$FormOf.formof(::typeof($name)) = $form_str
$name
end
end
end
Main.FormOf
julia> FormOf.#paramfn borda_score_fn(p) = exp(1/p)
borda_score_fn (generic function with 1 method)
julia> FormOf.formof(borda_score_fn)
"exp(1 / p)"
However, since it defines a new method of FormOf.formof, this only works in global scope:
julia> function bla()
FormOf.#paramfn fn(p) = exp(1/p)
fn(10) + 1
end
ERROR: syntax: Global method definition around REPL[45]:10 needs to be placed at the top level, or use "eval".
Stacktrace:
[1] top-level scope
# REPL[50]:1
#cbk's solution does not have this limitation.
To help my debugging (and also in order to better understand how Julia macros
work), I'm trying to define a simple macro that sourrounds blocks of code with
"Entering" and "Leaving" notifications. Here is what I've come up with so far:
macro dbg(block_title, expr)
quote
title = $block_title
println("Entering $title")
$expr
println("Leaving $title")
end
end
At first glance, it seems to do what I want:
julia> #dbg "first test" begin
println("does it work?")
end
Entering first test
does it work?
Leaving first test
however, as soon as variables are involved, nothing works anymore and I get
UndefVarError for all variables accesses. It looks like
the scope inside and outside the macro are distinct:
julia> #dbg "initialization" begin
foo = rand(10)
println("foo = ", foo)
end
Entering initialization
foo = [0.9178016919066918, 0.6004694971609528, 0.5294790810682284, 0.04208146400653634, 0.09271603217172952, 0.2809448815925, 0.68236281020963, 0.8313876607106496, 0.07484095574744898, 0.14099531301938573]
Leaving initialization
julia> foo
ERROR: UndefVarError: foo not defined
What am I doing wrong?
In short, you're missing the notion of macro
hygiene and
in particular the esc
function.
Although this part of the documentation is a good read for anyone wanting to develop their own macros, let's try to expand a little on what macro hygiene does in this particular example, and how you can fix things.
A useful way to debug macros is provided by
#macroexpand:
julia> #macroexpand #dbg "initialization" begin
foo = rand(10)
println("foo = ", foo)
end
quote
#= REPL[1]:3 =#
var"#32#title" = "initialization"
#= REPL[1]:4 =#
Main.println("Entering $(var"#32#title")")
#= REPL[1]:5 =#
begin
#= REPL[5]:2 =#
var"#33#foo" = Main.rand(10)
#= REPL[5]:3 =#
Main.println("foo = ", var"#33#foo")
end
#= REPL[1]:6 =#
Main.println("Leaving $(var"#32#title")")
end
Leaving aside all comments within #= ... =# markers, we see almost the code we
wanted to get: the user code block has been surrounded with statements printing
the "Entering" and "Leaving" notifications. However, there is one notable
difference: variable names like foo or title have been
replaced with weirdly looking names like var"#33#foo" or var"#32#title". This is what is called
"macro hygiene", and it helps avoiding clashes that could occur between the
variables used in the macro itself (like title in this example), and the variables used in the code block
provided as an argument to the macro (like foo here). (Think for example what
would happen if you used you #dbg on a code block that defines a title variable.)
Julia errs on the side of caution, and protects in this way all variables
appearing in the macro. If you want to disable this for selected parts of the
expression produced by the macro, you can wrap these subexpressions inside the
esc
function. In your example, you should for example escape the user-provided
expression:
macro dbg(block_title, expr)
quote
title = $block_title
println("Entering $title")
$(esc(expr))
println("Leaving $title")
end
end
Now things should work like want them to:
julia> #dbg "initialization" begin
foo = rand(10)
println("foo = ", foo)
end
Entering initialization
foo = [0.2955287439482881, 0.8989053281359838, 0.27751430906108343, 0.4920810199867245, 0.7633806735297282, 0.34535540650110597, 0.7099231627594489, 0.39978144801175564, 0.9104888704503833, 0.1983996781283539]
Leaving initialization
julia> #dbg "computation" begin
foo .+= 1
end
Entering computation
Leaving computation
julia> foo
10-element Array{Float64,1}:
1.295528743948288
1.8989053281359838
1.2775143090610834
1.4920810199867245
1.7633806735297282
1.345355406501106
1.709923162759449
1.3997814480117556
1.9104888704503833
1.198399678128354
I'm trying to code a macro which calls a function defined within the same module (with Julia v1.1). I always get the ERROR: LoadError: UndefVarError: myModel not defined.
The code of the module is the following (Models.jl):
module Models
export Model
export createmodel
export add_variable
export #add_variable
mutable struct Model
variable_symbols::Dict{String, Symbol}
variable_data::Dict{Symbol, Array{Int64, 1}}
Model(; variable_symbols = Dict([]), variable_data = Dict([])) =
new(variable_symbols, variable_data)
Model(variable_symbols, variable_data) = new(variable_symbols,
variable_data)
end
function createmodel()
model = Model()
return model
end
function add_variable(model, label::String, value::Array{Int64, 1})
symbol = Symbol(label)
model.variable_symbols[label] = Symbol(symbol)
model.variable_data[symbol] = value
end
macro add_variable(model, var)
quote
add_variable($model, $(string(var)), $var)
end
end
end
The macro is called as following:
include("Models.jl")
using .Models
x = [1, 2, 3]
myModel = createmodel()
#add_variable(myModel, x)
show(myModel)
I know that this is an escaping/hygiene problem, but after trying many ideas I don't get the expected result. Up to now, my only way to get the expected result was to define the macro outside of the module to get: Model(Dict("x"=>:x), Dict(:x=>[1, 2, 3]))
You just need to escape the variables that get passed into the macro:
macro add_variable(model, var)
quote
add_variable($(esc(model)), $(string(var)), $(esc(var)))
end
end
All names in a macro are resolved in the context of the module its defined in unless you esc them — with an esc they're resolved in the context of the caller. You almost always want to esc the arguments to the macro. Names like add_variable, though, you want to resolve in the place where the macro itself was defined.
If I have a method
macro doarray(arr)
if in(:head, fieldnames(typeof(arr))) && arr.head == :vect
println("A Vector")
else
throw(ArgumentError("$(arr) should be a vector"))
end
end
it works if I write this
#doarray([x])
or
#doarray([:x])
but the following code rightly does not work, raising the ArgumentError(i.e. ArgumentError: alist should be a vector).
alist = [:x]
#doarray(alist)
How can I make the above to act similarly as #doarray([x])
Motivation:
I have a recursive macro(say mymacro) which takes a vector, operates on the first value and then calls recursively mymacro with the rest of the vector(say rest_vector). I can create rest_vector, print the value correctly(for debugging) but I don't know how to evaluate rest_vector when I feed it to the mymacro again.
EDIT 1:
I'm trying to implement logic programming in Julia, namely MiniKanren. In the Clojure implementation that I am basing this off, the code is such.
(defmacro fresh
[var-vec & clauses]
(if (empty? var-vec)
`(lconj+ ~#clauses)
`(call-fresh (fn [~(first var-vec)]
(fresh [~#(rest var-vec)]
~#clauses)))))
My failing Julia code based on that is below. I apologize if it does not make sense as I am trying to understand macros by implementing it.
macro fresh(varvec, clauses...)
if isempty(varvec.args)
:(lconjplus($(esc(clauses))))
else
varvecrest = varvec.args[2:end]
return quote
fn = $(esc(varvec.args[1])) -> #fresh($(varvecvest), $(esc(clauses)))
callfresh(fn)
end
end
end
The error I get when I run the code #fresh([x, y], ===(x, 42))(you can disregard ===(x, 42) for this discussion)
ERROR: LoadError: LoadError: UndefVarError: varvecvest not defined
The problem line is fn = $(esc(varvec.args[1])) -> #fresh($(varvecvest), $(esc(clauses)))
If I understand your problem correctly it is better to call a function (not a macro) inside a macro that will operate on AST passed to the macro. Here is a simple example how you could do it:
function recarray(arr)
println("head: ", popfirst!(arr.args))
isempty(arr.args) || recarray(arr)
end
macro doarray(arr)
if in(:head, fieldnames(typeof(arr))) && arr.head == :vect
println("A Vector")
recarray(arr)
else
throw(ArgumentError("$(arr) should be a vector"))
end
end
Of course in this example we do not do anything useful. If you specified what exactly you want to achieve then I might suggest something more specific.