Is there some way to create a new closure from this where I modify the type and value of x? Something like,
g = deepcopy(f)
g.x = "2"
g() # would give "22"
The above does not work since you can’t set g.x, but gives you the idea of what I’m looking for. The reason I want this is basically because I have some closures with captured variables that are CuArrays that I’m trying to serialize, which later get deserialized on a machine with no GPU, so I want a way to pre-convert them to Arrays, but keep the original closure function as-is.
According to the devdocs, your code is almost identical to the code that defining the closure in the first place “roughly lowers to”, so I’m just hopeful that there’s a way to use that and avoid the boilerplate that results from writing it out by hand.
That’s interesting, I hadn’t realized you could do that. It kind of gives me a solution, although I think unfortunately I’d have to write “setter” functions for all my captured variables which I think which mostly defeats the purpose.
f = let x = 2
global setx(newx) = (x=newx)
() -> x^2
end
f() # gives 4
setx("2")
f() # gives "22"
Yes, but you have control over the naming of the structures, which is handy when you want to define the conversion. Does it add so much boilerplate code to write it like this? If you can use a parametric type it becomes only
julia> struct A{T}
x::T
end
julia> (a::A)() = a.x^2
julia> Base.convert(::Type{A{T}}, a::A) where T = A{T}(a.x)
julia> a = A(12.)
A{Float64}(12.0)
julia> a()
144.0
julia> convert(A{Int}, a)()
144
julia> A("x")()
"xx"
Thanks for the suggestions. I think I’ve got a solution that just uses the closure itself. Given that making the closure supposedly lowers to that struct, I was kind of confused why e.g. typeof(f)(3) wouldn’t let me construct a new instance of the closure where x was 3. I still don’t know why it doesn’t, but you can construct it by creating a “new” expression by hand and eval’ing it:
f = let x = 2
() -> x^2
end
g = eval(Expr(:new, typeof(f), 3))
g() # gives 9
g = eval(Expr(:new, typeof(f).name.wrapper{String}, "2"))
g() # gives "22"
And you can stick this in a @generated function that can then programatically scan through the closed over variables and convert the ones that are CuArrays.
Although a followup question I now have is why does eval(Expr(:new, typeof(f), 3)) work but not typeof(f)(3)? It would be nice if it did so I could avoid @generated world-age issues.
I believe this is because the struct that the closure is lowered to doesn’t have any default constructors. They are not normally required; lowering just uses a new expression to create the closure in situ.
Makes sense, I hadn’t appreciated the default constructor doesn’t always come for free.
Yes, but using a @generated function gets rid of that.
Nothing at all, its just that my real problem I have alot of these closures with many closed over variables all with different names, etc… so I was just looking for something more automatic.
FWIW, here’s more or less what the final solution looks like, here modifying an arbitrary number of closed over variables. I’m pretty impressed with Julia that its this simple and also completely type stable:
@generated function set_captured_vars(f::F, vals...) where {F<:Function}
Expr(:splatnew, F.name.wrapper{vals...}, :vals)
end
f = let x=2, y=3
() -> (x^2, y)
end
f() # gives (4,3)
g = set_captured_vars(f, "2", 3.)
g() # gives ("22", 3.00)
Yeah,
I was wanting a constructor for closures.
Cos they are basically functors (* kinda.)
If I had an instance I want to make a new instance with different closed variables.
I know it is possible,
but all I have is notes saying “Find the ccall that BSON.jl / Serialize uses to instanstiate them”.
Jeff says that is legit,
and the only reasom they don’t is to save time on creating the new methods.
IIRC it is ccall(:jl_newstructv or something like that.
But maybe it would be easier to use @marius311’s @generated function
julia> using CUDA, Adapt
julia> function foo(A)
bar() = A
bar
end
foo (generic function with 1 method)
julia> f = foo(rand(2,2))
(::var"#bar#1"{Matrix{Float64}}) (generic function with 1 method)
julia> f()
2×2 Matrix{Float64}:
0.167181 0.62589
0.174081 0.464358
julia> g = Adapt.adapt(CuArray, f)
(::var"#bar#1"{CuArray{Float64, 2}}) (generic function with 1 method)
julia> g()
2×2 CuArray{Float64, 2}:
0.167181 0.62589
0.174081 0.464358