There are two important reasons: I come from python and I’m also a newbie!! ;oP So, yes you are right I have changed the code to Julia’s ways.
I still have the problem nonetheless. I’ll try to think on a minimal example.
There are two important reasons: I come from python and I’m also a newbie!! ;oP So, yes you are right I have changed the code to Julia’s ways.
I still have the problem nonetheless. I’ll try to think on a minimal example.
This is a working example. I was trying to reproduce the problem that I have and I am not managing:
function create_function( funcname::String)
f_call = Expr( :call, Symbol(funcname), Expr(:(::),Symbol(:x),Int64) )
lista = :x
f_body = quote
newvalue = list2vsmap( $lista )
return newvalue
end
f_declare = Expr( :function, f_call, f_body )
eval( f_declare )
end
function list2vsmap( x::Int64)
x * 2
end
f = create_function( "myfunc" )
println("Testing my created function: $(f(2))")
In this short example, I was expecting list2vsmap to complain and say:
ERROR: LoadError: MethodError: no methoc matching list2vsmap(::Symbol)
but this short example works. While my other code doesn’t.
To give further insight, the key seems to be in the following bit of code that I have commented:
f_body = quote
# Creation of a VSMAP (kind of a dict)
lista = [] # lista stands for "list" in Spanish
for param in $params
if length(param) == 2 # This is mandatory arguments in f_call
tmp = (param[1], Symbol(param[1]) ) # Creates pairs such as: ("level", :level)
lista = [lista ; tmp]
elseif length(param) > 2 && param[3] == "opt" # This is for optional arguments in f_call
for (k,v) in kwargs # We check among the keywords arguments
if k == Symbol(param[1]) && !(v == nothing) # Only keywords not set to nothing are considered
tmp = (param[1], v) # Añadimos el parámetro con su valor
lista = [lista ; tmp]
end
end
end
end
if $funcname == "SetLogLevel"
eval(println(lista)) # This displays: Any[("level", :level)]
end
vsmap = list2vsmap( lista )
invoke( $ptr, $funcname, vsmap )
end
When I execute eval(println(lista))
I am getting what I want: a list of pairs where I have the name of the param and a Symbol whose value should come from the function call.
Later, list2vsmap looks as follows:
function list2vsmap( items ) #::Array{Any,1}
vsmap = createMap() # Creates a VSMAP (it is like a dictionary in "C")
for item in items # Iterates on the provided list of pairs; in the example is Any[("level", :level)]
key = item[1] # This is the "key"
value = item[2] # This is the value (a Symbol representing the function argument such as :level)
setvalue(vsmap, key, value) # Depending on the type of _value_ should call one method or another
end
vsmap
end
setvalue(vsmap::VSMap, key::String, value::Int) = propSetInt( vsmap, key, value, paAppend )
setvalue(vsmap::VSMap, key::String, value::AbstractFloat) = propSetFloat( vsmap, key, value, paAppend )
....
So list2vsmap converts the list into an VSMAP (see the comments).
What I don’t understand is why in this case setvalue is finding that value is a ::Symbol while the short example above, finds that x is an Int64.
I have checked that the signature of the function created here looks as expected:
SetLogLevel(level::Int64;)
I have found where is the issue, but I don’t know how to solve it. The problem is in the definition of the function’s body (f_body).
I am creating a function with the signature:
SetLogLevel(level::Int64)
and I am calling it using level=1
as follows:
SetLogLevel(1)
When I modify f_body
as follows:
f_body = quote
if $funcname == "SetLogLevel"
eval(println("TEST1: $(level)")) # OK: TEST1: 1
tmp = Symbol("level")
eval(println("TEST2: $(tmp) $(typeof(tmp))" )) # OK: TEST2: level Symbol
eval(println("TEST3: ", eval(tmp) )) # NOK: ERROR: LoadError: UndefVarError: level not defined
end
it can be seen that:
level
is seen within f_body
and it values 1
.Symbol("level")
; we can see it represents level
and the type is Symbol
.Symbol("level")
it is not finding it in this context.So what am I doing wrong? What do I have to do to get the value of the argument in f_call
? Why level
works while eval(Symbol("level"))
doesn’t?
Note: I have just check that:
tmp = $(Symbol("level")) # In this case, tmp gets the value that I want: 1
but how can I get it from:
tmp = Symbol("level")
because $(tmp)
doesn’t work.
eval
works in global scope, so any variables used in the expression evaluated must be globally defined. This makes it a bit confusing to reason about plus it is usually not the best way to do what you want to do. The following example is probably what you are seeing:
julia> function f(a)
eval(:a)
end
f (generic function with 1 method)
julia> f(1)
ERROR: UndefVarError: a not defined
Stacktrace:
[1] top-level scope
[2] eval at .\boot.jl:319 [inlined]
[3] eval(::Symbol) at .\client.jl:389
[4] f(::Int64) at .\REPL[1]:2
[5] top-level scope at none:0
julia> function f(a)
eval(:(a = $a))
end
f (generic function with 1 method)
julia> f(1)
1
In the first case, we are just calling a
in global scope which is not defined. In the second case, we are calling a = 1
in global scope so we are defining a
. If you type a
in the REPL then you will get 1. I hope this helps.
I’m still not getting a solution.
I am looking for the following:
julia> function f(a)
local m = :($a)
m
end
f (generic function with 1 method)
julia> f(1)
1
but when I replace a
with Symbol("a")
:
julia> function f(a)
local m = :($(Symbol("a")))
m
end
f (generic function with 1 method)
julia> f(1)
:a
What do I have to do with Symbol("a")
in order to get 1
instead of :a
?
You can’t.
The only way you could get to a
from its symbol is evaling, but eval
works in the global scope, so that would look for an a
in the global scope and not in your function and not do what you want.
Maybe you should explain with a bit more details what you are trying to do (wrapping a library?), because it looks like there’s maybe something not quite right with the way you are approaching your problem (this metaprogramming business can be tricky).
Now we are getting somewhere. Now, I know I cannot follow this path.
I am wrapping a C library (Vapoursynth). I am creating a number of functions trying to keep a similar signature to the C ones.
I am invoking the C functions with a VSMap which is kind of an array of key/values pairs in C. I get those arguments through the function signature. But I am not managing to get the functions arguments values when I am referencing them as symbols.
Maybe push and pop the values to a global vector, to keep the scope global? I don’t know how it translates to your function generator exactly, but consider:
A = Vector{Any}()
function f(a)
push!(A, a)
eval(pop!(A))
end
f(1)
Added: of course, you cannot push a Symbol of a local reference and still have the pop!ed Symbol have that local reference in global context. You have to pass the local value with push! and pop!.
Just out of curiosity, what’s the point of being able to define a function call programmatically if then you cannot refer to its arguments?
I don’t know what “refer to its arguments” mean.
Here is an example of some functions generated dynamically for reference
Dynamically generating functions just saves you from writing down a bunch of code that looks very similar (in the link above for 4 different number types), but in the end there is nothing special about a function that was generated from metaprogramming or by writing the body by hand.
So if you could just write down a few of the functions you want to generate by hand and then it should be easier to see how it can be done programatically. Preferably a bit simpler than in the first post of this thread.
Going back to your original question, if you are trying to create a function based on the argument types that are only known at run time, consider using generated functions.
I’ll try to explain better.
In the MKLSparse.jl example, the function signature is known. So you can use its arguments without problem. For example, line 35 displays argument “transa” which is later used in lines 38 and 39.
In my example, I am creating the function signature programatically (see the creation of f_call in my first post). Later I try to use those arguments creating symbols. Something like:
Symbol(param[1])
The documentation says:
In the context of an expression, symbols are used to indicate access to variables; when an expression is evaluated, a symbol is replaced with the value bound to that symbol in the appropriate scope.
But evaluations only take place on global space.
I think this post is clear regarding what I am trying to do. The MKLSparse.jl example fits on the first case of the post.
So I would reword my question as:
What is the point of being able to define a custom function signature if later I won’t be able to use the values of its arguments in the function’s body?
In fact, I would say that documentation (or the documentation) might be wrong:
julia> a = "hello"
julia> function m(a)
Symbol("a")
end
julia> m(1)
:a
julia> eval(m(1))
"hello"
I repeat what the documentation says:
[…] a symbol is replaced with the value bound to that symbol in the appropriate scope.
I would say that the scope of Symbol("a")
would be the function. And if not, how could I refer to the argument named “a” (bearing in mind that I am creating a bunch of functions and that the argument name is different in each case).
I am not sure if this is more clear now.
I changed this:
function list2vsmap( items ) #::Array{Any,1}
vsmap = createMap()
for item in items
key = item[1]
value = item[2]
#if typeof( value ) == Symbol
# value = esc(eval(value))
# println(value)
#end
if typeof( value ) <: AbstractFloat
propSetFloat( vsmap, key, value, paAppend )
elseif typeof( value ) <: Int
propSetInt( vsmap, key, value, paAppend )
elseif typeof( value ) <: Array{Int64,1}
propSetIntArray( vsmap, key, value )
elseif typeof( value ) <: Array{Float64,1}
propSetFloatArray( vsmap, key, value )
elseif typeof( value ) <: AbstractString
propSetData( vsmap, key, value, paAppend )
elseif typeof( value ) == VSNodeRef
propSetNode( vsmap, key, value, paAppend )
elseif typeof( value ) == VSFrameRef
propSetFrame( vsmap, key, value, paAppend )
elseif typeof( value ) == VSFuncRef
propSetFunc( vsmap, key, value, paAppend )
else
println("[ERROR] vsmap - list2vsmap: tipo no soportado: $(typeof(value))")
end
end
vsmap
end
into this:
function list2vsmap( items ) #::Array{Any,1}
vsmap = createMap()
for item in items
key = item[1]
value = item[2]
setvalue(vsmap, key, value)
end
vsmap
end
setvalue(vsmap::VSMap, key::String, value::Int) = propSetInt( vsmap, key, value, paAppend )
setvalue(vsmap::VSMap, key::String, value:: Array{Int64,1}) = propSetIntArray( vsmap, key, value )
setvalue(vsmap::VSMap, key::String, value::AbstractFloat) = propSetFloat( vsmap, key, value, paAppend )
setvalue(vsmap::VSMap, key::String, value:: Array{Float64,1}) = propSetFloatArray( vsmap, key, value )
setvalue(vsmap::VSMap, key::String, value::AbstractString) = propSetData( vsmap, key, value, paAppend )
setvalue(vsmap::VSMap, key::String, value::VSNodeRef) = propSetNode( vsmap, key, value, paAppend )
setvalue(vsmap::VSMap, key::String, value::VSFrameRef) = propSetFrame( vsmap, key, value, paAppend )
setvalue(vsmap::VSMap, key::String, value::VSFuncRef) = propSetFunc( vsmap, key, value, paAppend )
I am still learning!
What do I have to do with
Symbol("a")
in order to get1
instead of:a
?
Perhaps?
f(a) = a
or less trivially and maybe more useful:
x = :a
@eval begin
f($x) = $x
end
or with a macro:
macro f(x)
return esc(x)
end
called using @f(x)
or @f x
.
Also for sake of completeness, where you want to generate the body of f
for different types of a
, you can do:
@generated function f(a::T) where T
if T <: Integer
return :(a)
else
return :(error("Sorry, it is not clear what you want to do with the function's argument."))
end
end
This is pretty much every way to define the behavior of f
in Julia, see what suits you best.
Edit: the generated function was an overkill for the example above and should not be used in these cases. This is nothing more than a toy example.
There is still no need for any inner evals.
For example
function_name = :foobar
sig = (Int, Float64, Int32)
variables = [:a, :q, :d]
body = :(a + q * d)
function create_function_expr(function_name, sig, variables, body)
Expr(:function,
Expr(:call,
function_name,
[Expr(:(::), s, t) for (s, t) in zip(variables, sig)]...),
body
)
end
and using it:
julia> create_function_expr(function_name, sig, variables, body)
:(function foobar(a::Int64, q::Float64, d::Int32)
a + q * d
end)
julia> eval(create_function_expr(function_name, sig, variables, body))
foobar (generic function with 1 method)
julia> foobar(1, 2.0, Int32(3))
7.0
This looks much better. I will give it a try.
Very high level comment. Some people come to Julia from other dynamic languages and want to call eval inside of a function in order to do something specified by some end user in a dynamic way. Instead, the right approach in Julia is to splice a bit of end-user-specified code into a function definition, evaluate that function definition and then call that function as needed. The trick is to move the eval call outward. Hopefully that’s conceptually helpful.
I know the problem is how I am addressing Julia. For me “it feels bad” moving stuff outside. It feels like polluting the global space.
I will see tomorrow how it looks in my case.
Thanks for all your advices. They are all welcomed.