Any way to skip a constructor for Accessors.jl-like syntax?

Looks like this is actually not so hard to do by just defining a custom lens:

using Accessors: IndexLens, PropertyLens, ComposedOptic, setmacro, opticmacro, modifymacro

struct FieldLens{L}
    inner::L
end

(l::FieldLens)(o) = l.inner(o)
function Accessors.set(o, l::FieldLens{<:ComposedOptic}, val)
    o_inner = l.inner.inner(o)
    set(o_inner, FieldLens(l.inner.outer), val)
end
function Accessors.set(o, l::FieldLens{PropertyLens{prop}}, val) where {prop}
    setfield(o, val, Val(prop))
end

@generated function setfield(obj::T, val, ::Val{name}) where {T, name}
    fields = fieldnames(T)
    name ∈ fields || error("$(repr(name)) is not a field of $T, expected one of ", fields)
    Expr(:new, T, (name == field ? :val : :(getfield(obj, $(QuoteNode(field)))) for field ∈ fields)...)
end

macro setfield(ex)
    setmacro(FieldLens, ex, overwrite=false)
end
macro resetfield(ex)
    setmacro(FieldLens, ex, overwrite=true)
end

then define our type:

struct EvenInt
    x::Int
    function EvenInt(x::Int)
        iseven(x) || throw(ArgumentError("Must be even."))
        sleep(1) # pretend this is useful processing
        new(x)
    end
end

Regular @reset is still safe and slow:

julia> @time y = EvenInt(2)
  1.002596 seconds (4 allocations: 112 bytes)
EvenInt(2)

julia> @reset y.x = 5
ERROR: ArgumentError: Must be even.

julia> @time @reset y.x = 4
  1.002588 seconds (5 allocations: 128 bytes)
EvenInt(4)

But we now also have @resetfield which is unsafe and fast:

julia> @resetfield y.x = 5
EvenInt(5)

julia> @time @resetfield y.x = 4
  0.000005 seconds (1 allocation: 16 bytes)
EvenInt(4)
3 Likes