Constructing a function name and defining it is quite common (not just in my package). What confused me is the OP’s desire to construct a function name and look up its binding; where is that needed?
Indeed, I have done quite a bit of metaprogramming in Julia, and can’t recall the last time I had to get a function from a symbol. Typically this might suggest that macros are doing work at macro expansion time that should really be done by generated code at runtime instead.
Indeed, this was really useful discussion. Thank you.
There is still a point I don’t understand.
Suppose I have a lists of symbols, L1 = [:a,:b,:c]
and L2 = [:d,:e,:f]
, and I want to combine them to obtain a series of functions, ad()
, ae()
, etc. The only way I can do this is the following
for i in L1 j in L2
@eval $(Symbol(i,j))() = 0
end
And writing @eval Symbol($i,$j)() = 0
also doesn’t work. If there is a way to do this without using @eval
or in a better way please show me.
Yes, that’s right. $(Symbol(i,j))
constructs the combined symbol and then substitutes it into the code to evaluate, which is what you want.
In contrast, @eval Symbol($i,$j)() = 0
is equivalent to writing Symbol(a,d)() = 0
, which is not what you wanted.
Defining a bunch of functions from concatenated symbols in this way is actually pretty common in Julia, typically for writing wrapper code around external C and Fortran libraries that have lots of copy-and-paste permutations of a few symbols.
PS. Please read PSA: how to quote code with backticks
Thank you stevengj for your quick response. Can you explain why Symbol(a,b)() = 0
doesn’t work ? It seems that I need to use @eval to define a function name from a symbol, I really don’t understand what is going on.
The name of a function needs to be determined early on, even before it is compiled, and it would be too unpredictable to evaluate arbitrary code in order to do so (unless the user explicitly calls @eval
). As far as the Julia parser is concerned, Symbol(...)
is an arbitrary function call (since in the local namespace you could have defined Symbol
to be anything).
Also relevant to this discussion: reflection - Julia: invoke a function by a given string - Stack Overflow
Here I post a script that tries to interpret expressions provided in the form of strings and constructs the “corresponding” functions.
The problem comes from here.
But this post seems more suitable to host the (possible) discussion.
At the moment only some functions are “read” correctly.
The aim was however to build a small calculator for some predefined functions.
And in any case there is a case that causes the function that does the “parsing” to fail.
I have idea how to work around the problem, but I would like to ask for someone’s suggestion to have a “cleaner” solution.
la piccola calcolatatrice
julia> df=Dict("log"=>log, "sin"=>sin, "*"=>*,"+"=>+,"-"=>-,"^"=>^, "∘"=>∘)
Dict{String, Function} with 7 entries:
"∘" => ∘
"-" => -
"log" => log
"^" => ^
"*" => *
"+" => +
"sin" => sin
julia> function splitpar(str)
splitpos=[]
cp=0
for (i,c) in enumerate(str)
cp= cp+(c=='(')-(c==')')
c==','&& cp==0 && push!(splitpos,i)
end
splitpos
end
splitpar (generic function with 1 method)
julia> function splitinpos(s2s,pos)
ss=String[]
pos=[0;pos;length(s2s)+1]
for i in (2:length(pos))
push!(ss,s2s[pos[i-1]+1:pos[i]-1])
end
ss
end
splitinpos (generic function with 1 method)
julia> function str2func(str)
lff=findfirst('(', str)
op=df[str[1:lff-1]]
if !occursin('(',str[lff+1:end])
par=split(str[lff+1:end-1],',')
tp=tryparse.(Int,par)
n=only(filter(!isnothing,tp))
return ((a...)->op(n,a...))
else
s2s=str[lff+1:end-1]
par=splitinpos(s2s,splitpar(s2s))
return (x...)->op(
[str2func(p)(x[indexin(intersect(join(vars), p),vars)]
...) for p in par]...)
end
end
str2func (generic function with 1 method)
julia> vars=['x','y','z']
3-element Vector{Char}:
'x': ASCII/Unicode U+0078 (category Ll: Letter, lowercase)
'y': ASCII/Unicode U+0079 (category Ll: Letter, lowercase)
'z': ASCII/Unicode U+007A (category Ll: Letter, lowercase)
julia> str3= "+(*(+(3,x),*(-2,y)),*(2,x),+(*(3,x),*(4,y)))"
"+(*(+(3,x),*(-2,y)),*(2,x),+(*(3,x),*(4,y)))"
julia> str2func(str3)(1,-1)
9
julia> str4= "*(+(3,x),*(-2,z),*(1,x,z))"
"*(+(3,x),*(-2,z),*(1,x,z))"
julia> str2func(str4)(1,-1,2) #-32
-32
julia> str5= "+(*(+(3,x),*(-2,y),*(1,x,z)),*(+(3,y),*(-2,z),*(1,x,z)))"
"+(*(+(3,x),*(-2,y),*(1,x,z)),*(+(3,y),*(-2,z),*(1,x,z)))"
julia> str2func(str5)(1,-1,2) # 0
0
julia> str6= "+(*(+(3,x),*(-2,z),*(1,x)),*(+(3,y),*(-2,x),*(1,x,z)))"
"+(*(+(3,x),*(-2,z),*(1,x)),*(+(3,y),*(-2,x),*(1,x,z)))"
julia> str2func(str6)(1,-1,2)
ERROR: BoundsError: attempt to access Tuple{Int64, Int64} at index [3]
Stacktrace:
---
# the operation *(-2,z) indexes the 'z' which is in the third position, but the group *(+(3,x),*(-2,z),*(1,x)) it only has two distinct variables. The same expression with *(1,y) instead of *(1,x) does not in fact raise the same problem-
julia> str7= "+(*(+(3,x),*(-2,z),*(1,y)),*(+(3,y),*(-2,x),*(1,x,z)))"
"+(*(+(3,x),*(-2,z),*(1,y)),*(+(3,y),*(-2,x),*(1,x,z)))"
julia> str2func(str7)(1,-1,2)# 8
8
julia>