Closed dpsanders closed 2 years ago
Same issue for let
:
julia> ex = :(let a = 2
function f(x::Int)
return x + a
end
end)
:(let a = 2
#= REPL[6]:2 =#
function f(x::Int)
#= REPL[6]:2 =#
#= REPL[6]:3 =#
return x + a
end
end)
julia> eval(ex)(2)
4
julia> @RuntimeGeneratedFunction(ex)(2)
ERROR: ArgumentError: Function definition contains invalid function head `:let`
Expr
head: Symbol let
args: Array{Any}((2,))
1: Expr
head: Symbol =
args: Array{Any}((2,))
1: Symbol a
2: Int64 2
2: Expr
head: Symbol block
args: Array{Any}((2,))
1: LineNumberNode
line: Int64 2
file: Symbol REPL[6]
2: Expr
head: Symbol function
args: Array{Any}((2,))
1: Expr
head: Symbol call
args: Array{Any}((2,))
1: Symbol f
2: Expr
2: Expr
head: Symbol block
args: Array{Any}((3,))
1: LineNumberNode
2: LineNumberNode
3: Expr
@c42f is this solvable?
The block
example isn't a function. So I'm not sure what it's even meant to do?
The let
example defines a closure — it could be supported automatically, but there's a much more straightforward way — define the RGF with an extra parameter a
, then use a normal Julia closure to call the RGF:
julia> using RuntimeGeneratedFunctions
julia> RuntimeGeneratedFunctions.init(@__MODULE__)
julia> ex = :(function g(a, x)
return x + a
end)
g = @RuntimeGeneratedFunction(ex)
julia> f = let a = 2
x -> g(a, x)
end
#12 (generic function with 1 method)
julia> f(2)
4
Thanks @c42f . The way I'm currently doing things, the entire let
closure is generated as a string (by a python package) and then I apply Meta.parse
and evaluate. In particular the constants that go into the let
block (a bunch of sparse matrices and vectors) are also generated. I can easily generate the string that goes into the let block (e.g. a=2
) and the string for the internal function separately, but I'm not sure how to adapt the code to "evaluate" the string that goes into the let block in this case?
I can easily generate the string that goes into the let block
(e.g. a=2)
Ok, so presuming you've only got strings coming from python and you can't modify these, you can still extract the variable names as follows:
julia> bindings_str = "a=1, b=2, c=3"
"a=1, b=2, c=3"
julia> binding_names = [a.args[1] for a in Meta.parse("($bindings_str)").args]
3-element Vector{Symbol}:
:a
:b
:c
Then you need to construct the RGF function
argument list to include these extra variables as parameters. There is a subtle issue with this scheme when your bindings_str
is dynamically generated — you need a fixed wrapping closure to avoid world age issues.
This can be done as follows:
using RuntimeGeneratedFunctions
RuntimeGeneratedFunctions.init(@__MODULE__)
function make_rgf(bindings_str, body_str)
binding_names = [a.args[1] for a in Meta.parse("($bindings_str)").args]
main_body = Meta.parse("begin\n$body_str\nend")
param_tuple = eval(Meta.parse("($bindings_str)"))
ex = :(function f(params, x)
($(binding_names...),) = params
$main_body
end)
@info "Generating RGF" param_tuple ex
let params=param_tuple
g = @RuntimeGeneratedFunction(ex)
(x)->g(params, x)
end
end
Running this code, we have
julia> bindings_str_from_python = "a=1, b=2, c=3"
"a=1, b=2, c=3"
julia> body_str_from_python = "y = x*x\nreturn y + a + b"
"y = x*x\nreturn y + a + b"
julia> f = make_rgf(bindings_str_from_python, body_str_from_python)
┌ Info: Generating RGF
│ param_tuple = (a = 1, b = 2, c = 3)
│ ex =
│ :(function f(params, x)
│ #= /home/chris/rgf_test.jl:10 =#
│ #= /home/chris/rgf_test.jl:11 =#
│ (a, b, c) = params
│ #= /home/chris/rgf_test.jl:12 =#
│ begin
│ #= none:2 =#
│ y = x * x
│ #= none:3 =#
│ return y + a + b
│ end
└ end)
#21 (generic function with 1 method)
julia> f(2)
7
Thanks! I will try this out