In the REPL, the following code works:
julia> func = Meta.parse("x->x^2 + 2x - 1")
:(x->begin
#= none:1 =#
(x ^ 2 + 2x) - 1
end)
julia> map(eval(func), [1,3,-1])
3-element Array{Int64,1}:
2
14
-2
But if I put it into a function it fails:
julia> function test_map()
func = Meta.parse("x->x^2 + 2x - 1")
map(eval(func), [1,3,-1])
end
test_map (generic function with 1 method)
julia> test_map()
ERROR: MethodError: no method matching (::getfield(Main, Symbol("##9#10")))(::Int64)
The applicable method may be too new: running in world age 25572, while current world is 25573.
Closest candidates are:
#9(::Any) at none:1 (method too new to be called from this world context.)
Stacktrace:
[1] iterate at ./generator.jl:47 [inlined]
[2] _collect(::Array{Int64,1}, ::Base.Generator{Array{Int64,1},getfield(Main, Symbol("##9#10"))}, ::Base.EltypeUnknown, ::Base.HasShape{1}) at ./array.jl:619
[3] collect_similar(::Array{Int64,1}, ::Base.Generator{Array{Int64,1},getfield(Main, Symbol("##9#10"))}) at ./array.jl:548
[4] map(::Function, ::Array{Int64,1}) at ./abstractarray.jl:2018
[5] test_map() at ./REPL[8]:3
[6] top-level scope at none:0
julia>
Any idea?
Use invokelatest
if you want to call a dynamically generated function without hitting top-level scope between defining it and calling it. Code like this (defining functions by strings and evaling them) is often (but not always) a sign of some design error though.
5 Likes
I tried, but it didn’t work:
julia> function test_map()
func = Meta.parse("x->x^2 + 2x - 1")
map(Base.invokelatest(eval(func)), [1,3,-1])
end
test_map (generic function with 1 method)
julia> test_map()
ERROR: MethodError: no method matching (::getfield(Main, Symbol("##5#6")))()
Closest candidates are:
#5(::Any) at none:1
Stacktrace:
[1] #invokelatest#1 at ./essentials.jl:742 [inlined]
[2] invokelatest at ./essentials.jl:741 [inlined]
[3] test_map() at ./REPL[3]:3
[4] top-level scope at none:0
And this is NOT a design error, I just simplified my code for the forum. In reality I am assembling an expression at run-time (because the user must be able to edit it).
Any idea?
You just used it erroneously:
function test_map()
func = Meta.parse("x->x^2 + 2x - 1")
f = eval(func)
map(x -> Base.invokelatest(f, x), [1,3,-1])
end
There are many ways for users to “edit it”. Passing around strings that get evaluated is extremely seldom a good design choice.
4 Likes
Thank you, this fixed my problem.
As I said before, I am not passing around strings in my real code.
I think it has become standard to answer any question involving invokelatest
with some warning, because in the vast majority of cases things can be better designed by using macros. That said, there are still some cases where macros don’t apply.
@thautwarm has a library to help with this sort of thing:
https://github.com/thautwarm/GG.jl
The scoping rules are different, but if your use case fits this it can make things much faster.
I don’t see how the contention is between invokelatest
and macros
. Macros are just a way to make some syntax nicer to write while invokelatest
and eval
etc are about running code. Usually, the mistake is that people forget about e.g. passing in higher order functions etc.
2 Likes
Oh that’s interesting @kristoffer.carlsson. When I found the need to use invokelatest
for my work, lots of people told me I should be using macros. When that didn’t work, some suggested I should be using a macro that calls another macro. I haven’t thought much about improper use cases of invokelatest
, so I assumed people reaching for it must usually just need a macro
Okay, that sounds odd. I have no idea what a syntax transformation has to do with executing code.
I think it’s that a macro transforms syntax, but also executes the resulting code, and it’s one of very few paths from syntax to executable.
The limitation (and why we sometimes need things like invokelatest
) is that macro are only syntax, and can’t dereference. Say we have a function makeExpr
that returns an Expr
. It seems impossible to do something like
@someMacro makeExpr(foo)
and have this call makeExpr
and pass the result to @someMacro
. This is often the behavior we need for DSLs, which has caused trouble for a few of us (and led to development of GG.jl
).
Literally running code in Julia is the way to go from syntax to executable:
julia> 1+1
2
There is nothing you can do with macros that you couldn’t do by just manually writing the code they expanded to. They are therefore completely uninteresting when it comes to actually executing code (with some exception of special macros like @inbounds
and co where it is a bit tricky to just write the code after the macro).
Haha ok, fair enough.
This is really helpful perspective! Most documentation I’ve seen takes the view that macros are tricky but really powerful, without much discussion of their limitations. I’d guess it’s common for people to end up overestimating their capabilties
Chad said by macros, accurately it’s by @generated
macro. You just cannot expand them literally.
@generated
was included in the
since it is a very non-standard macro not writable by people themselves.
EDIT: cscherrer’s words:
because in the vast majority of cases things can be better designed by using macros.
IMO it’s okay for Chad to say this, and it’s okay for you to point out what is inproper.
Chad didn’t know @generated
is special, and according to my understanding of my previous talk with him, his friends just told him to “encode your models into type level and then use generated functions”.
This is the root cause for the increasing replies here
Interesting. I thought the go-to answer would have been to use a closure or higher-order function (e.g. function names are just objects too), but I guess macro makes sense too sometimes.
I think this is a matter of bias on my part. When I was struggling with this problem, I had been using closures and higher-order functions for a long time, and was comfortable with both. But macros were still new and mysterious. If there were suggestions to use the former (there may have been), I could more quickly see any flaw, so the interaction wouldn’t have been so memorable. With macros, my assumption was that everyone else knew the dark magic, and any confusion must be due to my lack of understanding.
How shall higher order functions help with my problem? I am working on a data base application and one requirement is that users must be able to assemble functions at runtime. The operations are simple, mainly +, - *, / and one possible function call. I am parsing the syntax tree to make sure no illegal operations are executed. I could build my own interpreter, but that is slower and more complicated that to use eval.
So how should higher order functions help in this case? Performance is not an issue at all compared to the time of the other parts of the program.
That is why I said often and not always.