Skip to content

Commit ea5dbc2

Browse files
committed
first pass get/set queries
1 parent 5757e6a commit ea5dbc2

File tree

3 files changed

+111
-1
lines changed

3 files changed

+111
-1
lines changed

src/optics.jl

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,3 +346,92 @@ end
346346
Base.@propagate_inbounds function set(obj, lens::DynamicIndexLens, val)
347347
return setindex(obj, val, lens.f(obj)...)
348348
end
349+
350+
abstract type AbstractQuery end
351+
352+
struct Query{F1,F2} <: AbstractQuery
353+
select::F1
354+
ignore::F2
355+
end
356+
Query(select) = Query(select, x -> false)
357+
Query(; select, ignore=x -> false) = Query(select, ignore)
358+
359+
queryargs(::Query, obj, fn, val) = (val,)
360+
361+
struct ParentQuery{F1,F2} <: AbstractQuery
362+
select::F1
363+
ignore::F2
364+
end
365+
ParentQuery(select) = ParentQuery(select, x -> false)
366+
ParentQuery(; select, ignore=x -> false) = ParentQuery(select, ignore)
367+
368+
queryargs(::ParentQuery, obj, fn, val) = (obj, fn)
369+
370+
@inline (optic::AbstractQuery)(obj) = _getall(obj, optic)
371+
@inline set(obj, optic::AbstractQuery, xs) = first(first(_setall(obj, optic, xs)))
372+
373+
struct Changed end
374+
struct Unchanged end
375+
376+
@inline function modify(f, obj, optic::AbstractQuery)
377+
set(obj, optic, map(f, optic(obj)))
378+
end
379+
380+
@generated function _getall(obj::O, optic::AbstractQuery) where O
381+
exp = Expr(:tuple)
382+
for fieldname in fieldnames(O)
383+
v = quote
384+
fn = $(QuoteNode(fieldname))
385+
val = getfield(obj, fn)
386+
args = queryargs(optic, obj, fn, val)
387+
if optic.select(args...)
388+
(val,)
389+
elseif !optic.ignore(args...)
390+
_getall(val, optic)
391+
else
392+
()
393+
end
394+
end
395+
# Splat the result into the output tuple
396+
push!(exp.args, Expr(:..., v))
397+
end
398+
exp
399+
end
400+
401+
@generated function _setall(obj::O, optic::AbstractQuery, xs, n=1) where O
402+
exp = Expr(:tuple)
403+
for fieldname in fieldnames(O)
404+
v = quote
405+
fn = $(QuoteNode(fieldname))
406+
val = getfield(obj, fn)
407+
args = queryargs(optic, obj, fn, val)
408+
if optic.select(args...)
409+
pair = xs[n] => Changed()
410+
n += 1
411+
pair
412+
elseif !optic.ignore(args...)
413+
pair, n = _setall(val, optic, xs, n)
414+
pair
415+
else
416+
val => Unchanged()
417+
end
418+
end
419+
push!(exp.args, v)
420+
end
421+
quote
422+
pairs = $exp
423+
vals = map(first, pairs)
424+
changes = map(last, pairs)
425+
_maybeconstruct(obj, vals, changes), n
426+
end
427+
end
428+
429+
@generated function _maybeconstruct(obj, vals, changes::C) where C
430+
if Changed in fieldtypes(C)
431+
:(_maybeconstruct(Changed(), obj, vals))
432+
else
433+
:(_maybeconstruct(Unchanged(), obj, vals))
434+
end
435+
end
436+
_maybeconstruct(::Changed, obj::O, vals) where O = constructorof(O)(vals...) => Changed()
437+
_maybeconstruct(::Unchanged, obj, vals) = obj => Unchanged()

test/test_optics.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ using Test
55

66
@testset "Properties" begin
77
pt = (x=1, y=2, z=3)
8-
@test (x=0, y=1, z=2) === @set pt |> Properties() -= 1
8+
@test (x=0, y=1, z=2) ===
9+
@set pt |> Properties(pt) -= 1
910
end
1011

1112
@testset "Elements" begin

test/test_queries.jl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using Accessors, ConstructionBase, BenchmarkTools
2+
3+
optic = Accessors.Query(x -> x isa Int)
4+
obj = (7, (a=17.0, b=2.0f0), ("3", 4, 5))
5+
vals = (1.0, 2.0, 3.0, 4.0)
6+
7+
@btime $optic($obj)
8+
@btime set($obj, $optic, $vals)
9+
# Compiles away
10+
@btime modify(x -> 2x, $vals, $optic)
11+
12+
13+
unstable_optic = Accessors.Query(x -> x isa Number && x > 2)
14+
15+
# This is slow
16+
@btime set($x, $unstable_optic, $y)
17+
18+
# This still compiles away
19+
@btime modify(x -> 2x, $vals, $unstable_optic)
20+

0 commit comments

Comments
 (0)