jw3126 / Setfield.jl

Update deeply nested immutable structs.
Other
167 stars 17 forks source link

SettableRef object #56

Open cstjean opened 5 years ago

cstjean commented 5 years ago

It occurred to me this morning that with the new setproperty!/getproperty methods, it might be possible to do something like:

o = SettableRef(some_immutable_object)
o.f.x = 10      
o.f.y = 23
some_immutable_object = get(o)   # unwrap the object

as syntax equivalent to

@set some_immutable_object.f.x = 10
@set some_immutable_object.f.y = 23

Internally, o.obj would contain the immutable, and o.f (getproperty(o, :f)) would return a proxy object p so that p.x = 10 (setproperty(p, :x, 10)) would be equivalent to o.obj = @set o.f.x = 10.

It's not a great win over @set, which is already pretty concise. But I think for us it would be interesting, as it turns an immutable object into (what looks like) a regular mutable object, without any scary macro involved. Was that discussed anywhere?

jw3126 commented 5 years ago

It is an interesting idea. AFAICT it was not discussed before. Some things that are challenging with this approach are:

tkf commented 5 years ago

Here is a super simple implementation without nested assignment support:

using Setfield: set, PropertyLens

mutable struct Mutable{T}
    value::T
end

thaw(imut) = Mutable(imut)
freeze(mut::Mutable) = getfield(mut, :value)

Base.setproperty!(mut::Mutable, name::Symbol, value) =
    setfield!(mut, :value, set(freeze(mut), PropertyLens{name}(), value))

Base.getproperty(mut::Mutable, name) = getproperty(freeze(mut), name)

It looks like the compiler can eliminate the allocation in a simple case like this:

f() = f((a=1,)).a

function f(x)
    y = thaw(x)
    y.a = 2
    return freeze(y)
end

@code_typed optimize=true f()

prints

CodeInfo(
1 ─     return 2
) => Int64
  • Change the type of the object

Maybe not being able to change the type is a feature in some sense? The user is signaling the compiler that the type never changes in this API. So it may be possible that the compiler has easier time inferring the code? This is just a speculation, though.

tkf commented 5 years ago

(I used thaw/freeze (inspired by https://github.com/JuliaLang/julia/pull/31630) instead of Ref-like API because it also makes sense to wrap Tuple/NamedTuple/StaticArray/etc. in this API.)