Skip to content

Commit a703494

Browse files
committed
get/set queries
1 parent 07e860c commit a703494

File tree

4 files changed

+321
-32
lines changed

4 files changed

+321
-32
lines changed

src/optics.jl

Lines changed: 145 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export @optic
22
export set, modify
33
export , opcompose, var"⨟"
4-
export Elements, Recursive, If, Properties
4+
export Elements, Recursive, Query, If, Properties
55
export setproperties
66
export constructorof
77
using ConstructionBase
@@ -125,13 +125,40 @@ function _set(obj, optic, val, ::SetBased)
125125
)
126126
end
127127

128+
<<<<<<< HEAD
128129
if VERSION < v"1.7"
129130
struct Returns{V}
130131
value::V
131132
end
132133
(o::Returns)(x) = o.value
133134
else
134135
using Base: Returns
136+
=======
137+
138+
struct Changed end
139+
struct Unchanged end
140+
141+
struct MaybeConstruct end
142+
_constructor(::MaybeConstruct, ::Type{T}) where T = constructorof(T)
143+
144+
struct List end
145+
_constructor(::List, ::Type) = tuple
146+
147+
struct Splat end
148+
_constructor(::Splat, ::Type) = _splat_all
149+
150+
_splat_all(args...) = _splat_all(args)
151+
@generated function _splat_all(args::A) where A<:Tuple
152+
exp = Expr(:tuple)
153+
for i in fieldnames(A)
154+
push!(exp.args, Expr(:..., :(args[$i])))
155+
end
156+
exp
157+
end
158+
159+
160+
struct Constant{V}
161+
value::V
135162
end
136163

137164
@inline function _set(obj, optic, val, ::ModifyBased)
@@ -189,9 +216,7 @@ $EXPERIMENTAL
189216
struct Elements end
190217
OpticStyle(::Type{<:Elements}) = ModifyBased()
191218

192-
function modify(f, obj, ::Elements)
193-
map(f, obj)
194-
end
219+
modify(f, obj, ::Elements) = map(f, obj)
195220

196221
"""
197222
If(modify_condition)
@@ -222,8 +247,36 @@ function modify(f, obj, w::If)
222247
end
223248
end
224249

250+
abstract type ObjectMap end
251+
252+
OpticStyle(::Type{<:ObjectMap}) = ModifyBased()
253+
modify(f, o, optic::ObjectMap) = mapobject(f, o, optic, Construct)
254+
255+
"""
256+
Properties()
257+
258+
Access all properties of an objects.
259+
260+
```jldoctest
261+
julia> using Accessors
262+
263+
julia> obj = (a=1, b=2, c=3)
264+
(a = 1, b = 2, c = 3)
265+
266+
julia> set(obj, Properties(), "hi")
267+
(a = "hi", b = "hi", c = "hi")
268+
269+
julia> modify(x -> 2x, obj, Properties())
270+
(a = 2, b = 4, c = 6)
271+
```
272+
Based on [`mapobject`](@ref).
273+
274+
$EXPERIMENTAL
275+
"""
276+
struct Properties <: ObjectMap end
277+
225278
"""
226-
mapproperties(f, obj)
279+
mapobject(f, obj)
227280
228281
Construct a copy of `obj`, with each property replaced by
229282
the result of applying `f` to it.
@@ -233,7 +286,7 @@ julia> using Accessors
233286
234287
julia> obj = (a=1, b=2);
235288
236-
julia> Accessors.mapproperties(x -> x+1, obj)
289+
julia> Accessors.mapobject(x -> x+1, obj)
237290
(a = 2, b = 3)
238291
```
239292
@@ -255,32 +308,20 @@ function mapproperties(f, obj)
255308
nt = getproperties(obj)
256309
patch = mapproperties(f, nt)
257310
return setproperties(obj, patch)
258-
end
259-
260-
"""
261-
Properties()
262-
263-
Access all properties of an objects.
264311

265-
```jldoctest
266-
julia> using Accessors
267-
268-
julia> obj = (a=1, b=2, c=3)
269-
(a = 1, b = 2, c = 3)
270-
271-
julia> set(obj, Properties(), "hi")
272-
(a = "hi", b = "hi", c = "hi")
273-
274-
julia> modify(x -> 2x, obj, Properties())
275-
(a = 2, b = 4, c = 6)
276-
```
277-
Based on [`mapproperties`](@ref).
312+
# Don't construct when we don't absolutely have to.
313+
# `constructorof` may not be defined for an object.
314+
@generated function _maybeconstruct(obj::O, props::P, handler::H) where {O,P,H}
315+
ctr = _constructor(H(), O)
316+
if Changed in map(last fieldtypes, fieldtypes(P))
317+
:($ctr(map(first, props)...) => Changed())
318+
else
319+
:(obj => Unchanged())
320+
end
321+
end
278322

279-
$EXPERIMENTAL
280-
"""
281-
struct Properties end
282-
OpticStyle(::Type{<:Properties}) = ModifyBased()
283-
modify(f, o, ::Properties) = mapproperties(f, o)
323+
skip(::Splat) = true
324+
skip(x) = false
284325

285326
"""
286327
Recursive(descent_condition, optic)
@@ -318,6 +359,80 @@ function _modify(f, obj, r::Recursive, ::ModifyBased)
318359
end
319360
end
320361

362+
abstract type AbstractQuery end
363+
364+
"""
365+
Query(select, descend, optic)
366+
367+
Query an object recursively, choosing fields when `select`
368+
returns `true`, and descending when `descend`.
369+
370+
```jldoctest
371+
julia> using Accessors
372+
373+
julia> obj = (a=missing, b=1, c=(d=missing, e=(f=missing, g=2)))
374+
(a = missing, b = 1, c = (d = missing, e = (f = missing, g = 2)))
375+
376+
julia> set(obj, Query(ismissing), (1.0, 2.0, 3.0))
377+
(a = 1.0, b = 1, c = (d = 2.0, e = (f = 3.jjjjjjtk,rg, g = 2)))
378+
379+
julia> obj = (1,2,(3,(4,5),6))
380+
(1, 2, (3, (4, 5), 6))
381+
382+
julia> modify(x -> 100x, obj, Recursive(x -> (x isa Tuple), Elements()))
383+
(100, 200, (300, (400, 500), 600))
384+
```
385+
$EXPERIMENTAL
386+
"""
387+
struct Query{Select,Descend,Optic<:Union{ComposedOptic,Properties}} <: AbstractQuery
388+
select_condition::Select
389+
descent_condition::Descend
390+
optic::Optic
391+
end
392+
Query(select, descend = x -> true) = Query(select, descend, Properties())
393+
Query(; select=Any, descend=x -> true, optic=Properties()) = Query(select, descend, optic)
394+
395+
OpticStyle(::Type{<:AbstractQuery}) = SetBased()
396+
397+
@inline function (q::AbstractQuery)(obj)
398+
let obj=obj, q=q
399+
mapobject(obj, _inner(q.optic), Splat()) do o
400+
if q.select_condition(o)
401+
(_getouter(o, q.optic),)
402+
elseif q.descent_condition(o)
403+
q(o) # also a tuple
404+
else
405+
()
406+
end
407+
end
408+
end
409+
end
410+
411+
set(obj, q::AbstractQuery, vals) = _set(obj, q, (vals, 1))[1][1]
412+
413+
@inline function _set(obj, q::AbstractQuery, (vals, itr))
414+
let obj=obj, q=q, vals=vals, itr=itr
415+
mapobject(obj, _inner(q.optic), MaybeConstruct(), itr) do o, itr::Int
416+
if q.select_condition(o)
417+
_setouter(o, q.optic, vals[itr]) => Changed(), itr + 1
418+
elseif q.descent_condition(o)
419+
_set(o, q, (vals, itr)) # Will be marked as Changed()/Unchanged()
420+
else
421+
o => Unchanged(), itr
422+
end
423+
end
424+
end
425+
end
426+
427+
modify(f, obj, q::Query) = set(obj, q, map(f, q(obj)))
428+
429+
@inline _inner(optic::ComposedOptic) = optic.inner
430+
@inline _inner(optic) = optic
431+
@inline _getouter(o, optic::ComposedOptic) = optic.outer(o)
432+
@inline _getouter(o, optic) = o
433+
@inline _setouter(o, optic::ComposedOptic, v) = set(o, optic.outer, v)
434+
@inline _setouter(o, optic, v) = v
435+
321436
################################################################################
322437
##### Lenses
323438
################################################################################

src/sugar.jl

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export @set, @optic, @reset, @modify
1+
export @set, @optic, @reset, @modify, @getall, @setall
22
using MacroTools
33

44
"""
@@ -84,7 +84,6 @@ end
8484
This function can be used to create a customized variant of [`@modify`](@ref).
8585
See also [`opticmacro`](@ref), [`setmacro`](@ref).
8686
"""
87-
8887
function modifymacro(optictransform, f, obj_optic)
8988
f = esc(f)
9089
obj, optic = parse_obj_optic(obj_optic)
@@ -94,6 +93,75 @@ function modifymacro(optictransform, f, obj_optic)
9493
end)
9594
end
9695

96+
"""
97+
@getall f(obj, arg...)
98+
@setall [x for x in obs if x isa Number] = values
99+
100+
@getall obj isa Number
101+
"""
102+
macro getall(ex)
103+
getallmacro(ex)
104+
end
105+
macro getall(ex, descend)
106+
getallmacro(ex; descend)
107+
end
108+
109+
function getallmacro(ex; descend=true)
110+
# Wrap descend in an anonoymous function
111+
descend = :(descend -> $descend)
112+
if @capture(ex, (lens_ for var_ in obj_ if select_))
113+
select = _select(select, var)
114+
optic =_optics(lens)
115+
:(Query($select, $descend, $optic)($(esc(obj))))
116+
elseif @capture(ex, [lens_ for var_ in obj_ if select_])
117+
select = _select(select, var)
118+
optic =_optics(lens)
119+
:([Query($select, $descend, $optic)($(esc(obj)))...])
120+
elseif @capture(ex, (lens_ for var_ in obj_))
121+
select = _ -> false
122+
optic = _optics(lens)
123+
:(Query($select, $descend, $optic)($(esc(obj))))
124+
elseif @capture(ex, [lens_ for var_ in obj_])
125+
select = _ -> false
126+
optic = _optics(lens)
127+
:([Query($select, $descend, $optic)($(esc(obj)))...])
128+
else
129+
error("@getall must be passed a generator")
130+
end
131+
end
132+
133+
# Turn this into an anonoymous function so it
134+
# doesn't matter which argument val is in
135+
_select(select, val) = :($(esc(val)) -> $(esc(select)))
136+
function _optics(ex)
137+
obj, optic = parse_obj_optic(ex)
138+
:($optic Fields())
139+
end
140+
141+
142+
"""
143+
@setall f(obj, arg...) = values
144+
145+
@setall [x for x in obs if x isa Number] = values
146+
147+
"""
148+
macro setall(ex)
149+
setallmacro(ex)
150+
end
151+
152+
function setallmacro(ex)
153+
if @capture(ex, ((lens_ for var_ in obj_ if select_) = vals_))
154+
select = _select(select, var)
155+
optic =_optics(lens)
156+
:(set($(esc(obj)), Query(; select=$select, optic=$optic), $(esc(vals))))
157+
elseif @capture(ex, ((lens_ for var_ in obj_) = vals_))
158+
optic = _optics(lens)
159+
:(set($(esc(obj)), Query(; optic=$optic), $(esc(vals))))
160+
else
161+
error("@getall must be passed a generator")
162+
end
163+
end
164+
97165
foldtree(op, init, x) = op(init, x)
98166
foldtree(op, init, ex::Expr) =
99167
op(foldl((acc, x) -> foldtree(op, acc, x), ex.args; init=init), ex)

test/test_optics.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ end
4343
pt = Point(1f0, 2e0, 3)
4444
pt2 = @inferred modify(x->2x, pt, Properties())
4545
@test pt2 === Point(2f0, 4e0, 6)
46+
@test (x=0, y=1, z=2) ===
47+
@set pt |> Properties(pt) -= 1
4648
end
4749

4850
@testset "Elements" begin

0 commit comments

Comments
 (0)