Skip to content

Commit e6e851b

Browse files
committed
fix get
1 parent 0f86996 commit e6e851b

File tree

2 files changed

+57
-50
lines changed

2 files changed

+57
-50
lines changed

src/optics.jl

Lines changed: 52 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -129,24 +129,14 @@ end
129129
struct Changed end
130130
struct Unchanged end
131131

132-
struct ConstructIfChanged{C}
133-
constructor::C
134-
end
135-
136-
# TODO what do we call these things?
137-
struct Construct end
138-
_constructor(::Construct, ::Type{T}) where T = constructorof(T)
139-
140132
struct MaybeConstruct end
141-
function _constructor(::MaybeConstruct, ::Type{T}) where T
142-
ConstructIfChanged(constructorof(T))
143-
end
133+
_constructor(::MaybeConstruct, ::Type{T}) where T = constructorof(T)
144134

145135
struct List end
146136
_constructor(::List, ::Type) = tuple
147137

148-
struct Splat end
149-
_constructor(::Splat, ::Type) = _splat_all
138+
struct Skip end
139+
_constructor(::Skip, ::Type) = _splat_all
150140

151141
_splat_all(args...) = _splat_all(args)
152142
@generated function _splat_all(args::A) where A<:Tuple
@@ -297,7 +287,7 @@ function mapobject(f, obj::O, ::Properties, handler, itr::Nothing) where O
297287
# TODO move this helper elsewhere?
298288
pnames = propertynames(obj)
299289
if isempty(pnames)
300-
return obj
290+
return _maybeskip(handler, obj)
301291
else
302292
new_props = map(pnames) do p
303293
f(getproperty(obj, p))
@@ -344,56 +334,69 @@ $EXPERIMENTAL
344334
"""
345335
struct Fields <: ObjectMap end
346336

347-
@generated function mapobject(f, obj::O, ::Fields, handler::H=Construct(), itr::I=nothing) where {O,H,I}
337+
@generated function mapobject(f, obj::O, ::Fields, handler::H, itr::Nothing) where {O,H,I}
348338
# TODO: This is how Flatten.jl works, but it's not really
349339
# correct use of ConstructionBase as it assumers properties=fields
350340
fnames = fieldnames(O)
351341
ctr = _constructor(H(), O)
352342
if isempty(fnames)
353-
:(return _maybeitr(obj, itr))
343+
:(return _maybeskip(handler, obj))
354344
else
355345
prop_args = map(fn -> :(getfield(obj, $(QuoteNode(fn)))), fnames)
356346
prop_exp = Expr(:tuple, prop_args...)
357-
if I === Nothing
358-
new_prop_exp = Expr(:tuple, map(pa -> :(f($pa)), prop_args)...)
359-
else
360-
### Unrolled iterating function appliation (it will compile away) ####
361-
# Each function call also updates the iterator value in local scoope with
362-
# the return value from the function. But it only actually inserts the
363-
# value into the parent tuple.
364-
val_exps = map(prop_args) do pa
365-
:((val, itr) = f($pa, itr); val)
366-
end
367-
new_prop_exp = Expr(:tuple, val_exps...)
347+
new_prop_exp = Expr(:tuple, map(pa -> :(f($pa)), prop_args)...)
348+
quote
349+
props = $prop_exp
350+
new_props = $new_prop_exp
351+
return $ctr(new_props...)
368352
end
369-
ret = if H == MaybeConstruct
370-
quote
371-
# TODO: last type instability.
372-
# replace this with val => Changed(), val => Unchanged()
373-
# return values.
374-
#
375-
# Don't construct when we don't absolutely have to.
376-
# `constructorof` may not be defined for an object.
377-
if props === new_props
378-
return _maybeitr(obj, itr)
379-
else
380-
return _maybeitr($ctr(new_props...), itr)
381-
end
382-
end
383-
else
384-
ret = :(return _maybeitr($ctr(new_props...), itr))
353+
end
354+
end
355+
@generated function mapobject(f, obj::O, ::Fields, handler::H, itr::Int) where {O,H}
356+
# TODO: This is how Flatten.jl works, but it's not really
357+
# correct use of ConstructionBase as it assumers properties=fields
358+
fnames = fieldnames(O)
359+
ctr = _constructor(H(), O)
360+
if isempty(fnames)
361+
:(return (obj, itr) => Unchanged())
362+
else
363+
prop_args = map(fn -> :(getfield(obj, $(QuoteNode(fn)))), fnames)
364+
prop_exp = Expr(:tuple, prop_args...)
365+
### Unrolled iterating function appliation (it will compile away) ####
366+
# Each function call also updates the iterator value in local scoope with
367+
# the return value from the function. But it only actually inserts the
368+
# value into the parent tuple.
369+
val_exps = map(prop_args) do pa
370+
:(((val, itr), change) = f($pa, itr); val => change)
385371
end
372+
new_prop_exp = Expr(:tuple, val_exps...)
386373
quote
387374
props = $prop_exp
388375
new_props = $new_prop_exp
389-
$ret
376+
new_props, change = _splitchanged(new_props)
377+
# Don't construct when we don't absolutely have to.
378+
# `constructorof` may not be defined for an object.
379+
if change isa Changed
380+
return ($ctr(new_props...), itr) => change
381+
else
382+
return (obj, itr) => change
383+
end
390384
end
391385
end
392386
end
393387

388+
_splitchanged(props) = map(first, props), _findchanged(map(last, props))
389+
390+
_findchanged(::Tuple{Changed,Vararg}) = Changed()
391+
_findchanged(cs::Tuple) = _findchanged(Base.tail(cs))
392+
_findchanged(::Tuple{}) = Unchanged()
393+
394394
_maybeitr(x, ::Nothing) = x
395395
_maybeitr(x, itr) = x, itr
396396

397+
_maybeskip(::Skip, v) = ()
398+
_maybeskip(x, v) = v
399+
397400
"""
398401
Recursive(descent_condition, optic)
399402
@@ -499,7 +502,7 @@ Query(select, descend = x -> true) = Query(select, descend, Fields())
499502
Query(; select=Any, descend=x -> true, optic=Fields()) = Query(select, descend, optic)
500503

501504
function (q::Query)(obj)
502-
mapobject(obj, _inner(q.optic), Splat(), nothing) do o
505+
mapobject(obj, _inner(q.optic), Skip(), nothing) do o
503506
if q.select_condition(o)
504507
(_getouter(o, q.optic),)
505508
elseif q.descent_condition(o)
@@ -510,16 +513,16 @@ function (q::Query)(obj)
510513
end
511514
end
512515

513-
set(obj, q::Query, vals) = _set(obj, q::Query, (vals, 1))[1]
516+
set(obj, q::Query, vals) = _set(obj, q::Query, (vals, 1))[1][1]
514517

515518
function _set(obj, q::Query, (vals, itr))
516-
mapobject(obj, _inner(q.optic), Construct(), itr) do o, itr
519+
mapobject(obj, _inner(q.optic), MaybeConstruct(), itr) do o, itr
517520
if q.select_condition(o)
518-
_setouter(o, q.optic, vals[itr]), itr + 1
521+
(_setouter(o, q.optic, vals[itr]), itr + 1) => Changed()
519522
elseif q.descent_condition(o)
520523
_set(o, q, (vals, itr))
521524
else
522-
o, itr
525+
(o, itr) => Unchanged()
523526
end
524527
end
525528
end

test/test_queries.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,14 @@ println("get")
2626
@btime $slowlens($obj)
2727
@test lens(obj) == slowlens(obj) == (17.0, 6.0)
2828

29+
missings_obj = (a=missing, b=1, c=(d=missing, e=(f=missing, g=2)))
30+
@test Query(ismissing)(missings_obj) === (missing, missing, missing)
31+
2932
println("set")
3033
# Need a wrapper so we don't have to pass in the starting iterator
3134
@btime Accessors.set($obj, $lens, $vals)
3235
@btime Accessors._set($obj, $lens, ($vals, 1))[1]
33-
@btime Accessors.set($obj, $slowlens, $vals)
36+
# @btime Accessors.set($obj, $slowlens, $vals)
3437
Accessors.set(obj, lens, vals)
3538
@test Accessors.set(obj, lens, vals) ==
3639
Accessors.set(obj, lens, vals) ==
@@ -47,3 +50,4 @@ unstable_lens = Accessors.Query(select=x -> x isa Float64 && x > 2, descend=x ->
4750
# Somehow modify compiles away almost completely
4851
@btime modify(x -> 10x, $obj, $lens)
4952
@test modify(x -> 10x, obj, lens) == (7, (a=170.0, b=2.0f0), ("3", 4, 5.0), ((a=60.0,), [1]))
53+

0 commit comments

Comments
 (0)