Convert String to function name

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?

2 Likes

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.

2 Likes

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

2 Likes

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).

2 Likes

Also relevant to this discussion: reflection - Julia: invoke a function by a given string - Stack Overflow

3 Likes

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>