Skip to content

Commit 8fb1580

Browse files
committed
context
1 parent 83a1bd9 commit 8fb1580

File tree

4 files changed

+183
-132
lines changed

4 files changed

+183
-132
lines changed

src/optics.jl

Lines changed: 145 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export setproperties
66
export constructorof
77
using ConstructionBase
88
using CompositionsBase
9+
using Static
910
using Base: getproperty
1011
using Base
1112

@@ -125,15 +126,15 @@ function _set(obj, optic, val, ::SetBased)
125126
)
126127
end
127128

128-
<<<<<<< HEAD
129129
if VERSION < v"1.7"
130130
struct Returns{V}
131131
value::V
132132
end
133133
(o::Returns)(x) = o.value
134134
else
135135
using Base: Returns
136-
=======
136+
end
137+
137138

138139
struct Changed end
139140
struct Unchanged end
@@ -276,19 +277,7 @@ $EXPERIMENTAL
276277
struct Properties <: ObjectMap end
277278

278279
"""
279-
mapobject(f, obj)
280-
281-
Construct a copy of `obj`, with each property replaced by
282-
the result of applying `f` to it.
283-
284-
```jldoctest
285-
julia> using Accessors
286-
287-
julia> obj = (a=1, b=2);
288-
289-
julia> Accessors.mapobject(x -> x+1, obj)
290-
(a = 2, b = 3)
291-
```
280+
maproperties()
292281
293282
# Implementation
294283
@@ -300,8 +289,8 @@ $EXPERIMENTAL
300289
"""
301290
function mapproperties end
302291

303-
function mapproperties(f, nt::NamedTuple)
304-
map(f,nt)
292+
function mapproperties(f, nt::Union{Tuple,NamedTuple})
293+
map(f, nt)
305294
end
306295

307296
function mapproperties(f, obj)
@@ -310,17 +299,6 @@ function mapproperties(f, obj)
310299
return setproperties(obj, patch)
311300
end
312301

313-
# Don't construct when we don't absolutely have to.
314-
# `constructorof` may not be defined for an object.
315-
@generated function _maybeconstruct(obj::O, props::P, handler::H) where {O,P,H}
316-
ctr = _constructor(H(), O)
317-
if Changed in map(last fieldtypes, fieldtypes(P))
318-
:($ctr(map(first, props)...) => Changed())
319-
else
320-
:(obj => Unchanged())
321-
end
322-
end
323-
324302
skip(::Splat) = true
325303
skip(x) = false
326304

@@ -360,6 +338,59 @@ function _modify(f, obj, r::Recursive, ::ModifyBased)
360338
end
361339
end
362340

341+
"""
342+
343+
new_obj, new_state = modify_stateful(f, (obj,state), optic)
344+
345+
Here `f` has signature `f(::Value, ::State) -> Tuple{NewValue, NewState}`.
346+
"""
347+
function modify_stateful end
348+
349+
@inline function modify_stateful(f, (obj, state), optic::Properties)
350+
let f=f, obj=obj, state=state
351+
modify_stateful_context((obj, state), optic) do _, fn, pr, st
352+
f(getfield(pr, known(fn)), st)
353+
end
354+
end
355+
end
356+
357+
@generated function modify_stateful_context(f, (obj, state1)::T, optic::Properties) where T
358+
_modify_stateful_inner(T)
359+
end
360+
361+
# Separated for testing object/state combinations without restarts
362+
function _modify_stateful_inner(::Type{<:Tuple{O,S}}) where {O,S}
363+
modifications = []
364+
vals = Expr(:tuple)
365+
fns = fieldnames(O)
366+
local st1 = :state0
367+
local st2 = :state1
368+
for (i, fn) in enumerate(fns)
369+
v = Symbol("val$i")
370+
st1 = Symbol("state$i")
371+
st2 = Symbol("state$(i+1)")
372+
ms = if O <: Tuple
373+
:(($v, $st2) = f(obj, StaticInt{$(QuoteNode(fn))}(), props, $st1))
374+
else
375+
:(($v, $st2) = f(obj, StaticSymbol{$(QuoteNode(fn))}(), props, $st1))
376+
end
377+
push!(modifications, ms)
378+
push!(vals.args, v)
379+
end
380+
patch = O <: Tuple ? vals : :(NamedTuple{$fns}($vals))
381+
Expr(:block,
382+
:(props = getproperties(obj)),
383+
modifications...,
384+
:(patch = $patch),
385+
:(new_obj = maybesetproperties($st2, obj, patch)),
386+
:(new_state = maybesetstate($st2, obj, patch)),
387+
:(return (setproperties(obj, patch), $st2)),
388+
)
389+
end
390+
391+
maybesetproperties(state, obj, patch) = setproperties(obj, patch)
392+
maybesetstate(state, obj, patch) = state
393+
363394
abstract type AbstractQuery end
364395

365396
"""
@@ -395,44 +426,101 @@ Query(; select=Any, descend=x -> true, optic=Properties()) = Query(select, desce
395426

396427
OpticStyle(::Type{<:AbstractQuery}) = SetBased()
397428

398-
@inline function (q::AbstractQuery)(obj)
399-
let obj=obj, q=q
400-
mapobject(obj, _inner(q.optic), Splat()) do o
401-
if q.select_condition(o)
402-
(_getouter(o, q.optic),)
403-
elseif q.descent_condition(o)
404-
q(o) # also a tuple
405-
else
406-
()
407-
end
408-
end
429+
struct Context{Select,Descend,Optic<:Union{ComposedOptic,Properties}} <: AbstractQuery
430+
select_condition::Select
431+
descent_condition::Descend
432+
optic::Optic
433+
end
434+
435+
436+
struct ContextState{V}
437+
vals::V
438+
end
439+
struct GetAllState{V}
440+
vals::V
441+
end
442+
struct SetAllState{C,V,I}
443+
change::C
444+
vals::V
445+
itr::I
446+
end
447+
448+
pop(x) = first(x), Base.tail(x)
449+
push(x, val) = (x..., val)
450+
push(x::GetAllState, val) = GetAllState(push(x.vals, val))
451+
452+
(q::Query)(obj) = getall(obj, q)
453+
454+
function getall(obj, q)
455+
initial_state = GetAllState(())
456+
_, final_state = modify_stateful((obj, initial_state), q) do o, s
457+
new_state = push(s, outer(q.optic, o, s))
458+
o, new_state
459+
end
460+
return final_state.vals
461+
end
462+
463+
function setall(obj, q, vals)
464+
initial_state = SetAllState(Unchanged(), vals, 1)
465+
final_obj, _ = modify_stateful((obj, initial_state), q) do o, s
466+
new_output = outer(q.optic, o, s)
467+
new_state = SetAllState(Changed(), s.vals, s.itr + 1)
468+
new_output, new_state
469+
end
470+
return final_obj
471+
end
472+
473+
function context(f, obj, q)
474+
initial_state = GetAllState(())
475+
_, final_state = modify_stateful_context((obj, initial_state), Properties()) do o, fn, pr, s
476+
new_state = push(s, f(o, known(fn)))
477+
o, new_state
409478
end
479+
return final_state.vals
410480
end
411481

412-
set(obj, q::AbstractQuery, vals) = _set(obj, q, (vals, 1))[1][1]
482+
modify(f, obj, q::Query) = setall(obj, q, map(f, getall(obj, q)))
413483

414-
@inline function _set(obj, q::AbstractQuery, (vals, itr))
415-
let obj=obj, q=q, vals=vals, itr=itr
416-
mapobject(obj, _inner(q.optic), MaybeConstruct(), itr) do o, itr::Int
417-
if q.select_condition(o)
418-
_setouter(o, q.optic, vals[itr]) => Changed(), itr + 1
419-
elseif q.descent_condition(o)
420-
_set(o, q, (vals, itr)) # Will be marked as Changed()/Unchanged()
421-
else
422-
o => Unchanged(), itr
423-
end
484+
@inline function modify_stateful(f::F, (obj, state), q::Query) where F
485+
modify_stateful((obj, state), inner(q.optic)) do o, s
486+
if q.select_condition(o)
487+
f(o, s)
488+
elseif q.descent_condition(o)
489+
ds = descent_state(s)
490+
o, s = modify_stateful(f::F, (o, ds), q)
491+
o, merge_state(s, ds)
492+
else
493+
o, s
424494
end
425495
end
426496
end
427497

428-
modify(f, obj, q::Query) = set(obj, q, map(f, q(obj)))
498+
maybesetproperties(state::GetAllState, obj, patch) = obj
499+
maybesetproperties(state::SetAllState, obj, patch) =
500+
maybesetproperties(state.change, state, obj, patch)
501+
maybesetproperties(::Changed, state::SetAllState, obj, patch) = setproperties(obj, patch)
502+
maybesetproperties(::Unchanged, state::SetAllState, obj, patch) = obj
503+
504+
descent_state(state::SetAllState) = SetAllState(Unchanged(), state.vals, state.itr)
505+
descent_state(state) = state
506+
507+
merge_state(s1::SetAllState, s2) = SetAllState(anychanged(s1, s2), s2.vals, s2.itr)
508+
merge_state(s1, s2) = s2
509+
510+
anychanged(s1, s2) = anychanged(s1.change, s2.change)
511+
anychanged(::Unchanged, ::Unchanged) = Unchanged()
512+
anychanged(::Unchanged, ::Changed) = Changed()
513+
anychanged(::Changed, ::Unchanged) = Changed()
514+
anychanged(::Changed, ::Changed) = Changed()
515+
516+
inner(optic) = optic
517+
inner(optic::ComposedOptic) = optic.inner
518+
519+
outer(optic, o, state::GetAllState) = o
520+
outer(optic::ComposedOptic, o, state::GetAllState) = optic.outer(o)
521+
outer(optic::ComposedOptic, o, state::SetAllState) = set(o, optic.outer, state.vals[state.itr])
522+
outer(optic, o, state::SetAllState) = state.vals[state.itr]
429523

430-
@inline _inner(optic::ComposedOptic) = optic.inner
431-
@inline _inner(optic) = optic
432-
@inline _getouter(o, optic::ComposedOptic) = optic.outer(o)
433-
@inline _getouter(o, optic) = o
434-
@inline _setouter(o, optic::ComposedOptic, v) = set(o, optic.outer, v)
435-
@inline _setouter(o, optic, v) = v
436524

437525
################################################################################
438526
##### Lenses

src/setindex.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ Base.@propagate_inbounds function setindex(args...)
22
Base.setindex(args...)
33
end
44

5+
Base.@propagate_inbounds function setindex(xs::NamedTuple{K}, v, i::Int) where K
6+
Base.setindex(xs, v, K[i])
7+
end
8+
59
@inline setindex(::Base.RefValue, val) = Ref(val)
610

711
Base.@propagate_inbounds function setindex(xs::AbstractArray, v, I...)

src/sugar.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ end
135135
_select(select, val) = :($(esc(val)) -> $(esc(select)))
136136
function _optics(ex)
137137
obj, optic = parse_obj_optic(ex)
138-
:($optic Fields())
138+
:($optic Properties())
139139
end
140140

141141

@@ -153,10 +153,10 @@ function setallmacro(ex)
153153
if @capture(ex, ((lens_ for var_ in obj_ if select_) = vals_))
154154
select = _select(select, var)
155155
optic =_optics(lens)
156-
:(set($(esc(obj)), Query(; select=$select, optic=$optic), $(esc(vals))))
156+
:(setall($(esc(obj)), Query(; select=$select, optic=$optic), $(esc(vals))))
157157
elseif @capture(ex, ((lens_ for var_ in obj_) = vals_))
158158
optic = _optics(lens)
159-
:(set($(esc(obj)), Query(; optic=$optic), $(esc(vals))))
159+
:(setall($(esc(obj)), Query(; optic=$optic), $(esc(vals))))
160160
else
161161
error("@getall must be passed a generator")
162162
end

0 commit comments

Comments
 (0)