Skip to content

Commit

Permalink
first pass get/set queries
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaqz committed Jun 6, 2021
1 parent 5757e6a commit ea5dbc2
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 1 deletion.
89 changes: 89 additions & 0 deletions src/optics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -346,3 +346,92 @@ end
Base.@propagate_inbounds function set(obj, lens::DynamicIndexLens, val)
return setindex(obj, val, lens.f(obj)...)
end

abstract type AbstractQuery end

struct Query{F1,F2} <: AbstractQuery
select::F1
ignore::F2
end
Query(select) = Query(select, x -> false)
Query(; select, ignore=x -> false) = Query(select, ignore)

queryargs(::Query, obj, fn, val) = (val,)

struct ParentQuery{F1,F2} <: AbstractQuery
select::F1
ignore::F2
end
ParentQuery(select) = ParentQuery(select, x -> false)
ParentQuery(; select, ignore=x -> false) = ParentQuery(select, ignore)

queryargs(::ParentQuery, obj, fn, val) = (obj, fn)

@inline (optic::AbstractQuery)(obj) = _getall(obj, optic)
@inline set(obj, optic::AbstractQuery, xs) = first(first(_setall(obj, optic, xs)))

struct Changed end
struct Unchanged end

@inline function modify(f, obj, optic::AbstractQuery)
set(obj, optic, map(f, optic(obj)))
end

@generated function _getall(obj::O, optic::AbstractQuery) where O
exp = Expr(:tuple)
for fieldname in fieldnames(O)
v = quote
fn = $(QuoteNode(fieldname))
val = getfield(obj, fn)
args = queryargs(optic, obj, fn, val)
if optic.select(args...)
(val,)
elseif !optic.ignore(args...)
_getall(val, optic)
else
()
end
end
# Splat the result into the output tuple
push!(exp.args, Expr(:..., v))
end
exp
end

@generated function _setall(obj::O, optic::AbstractQuery, xs, n=1) where O
exp = Expr(:tuple)
for fieldname in fieldnames(O)
v = quote
fn = $(QuoteNode(fieldname))
val = getfield(obj, fn)
args = queryargs(optic, obj, fn, val)
if optic.select(args...)
pair = xs[n] => Changed()
n += 1
pair
elseif !optic.ignore(args...)
pair, n = _setall(val, optic, xs, n)
pair
else
val => Unchanged()
end
end
push!(exp.args, v)
end
quote
pairs = $exp
vals = map(first, pairs)
changes = map(last, pairs)
_maybeconstruct(obj, vals, changes), n
end
end

@generated function _maybeconstruct(obj, vals, changes::C) where C
if Changed in fieldtypes(C)
:(_maybeconstruct(Changed(), obj, vals))
else
:(_maybeconstruct(Unchanged(), obj, vals))
end
end
_maybeconstruct(::Changed, obj::O, vals) where O = constructorof(O)(vals...) => Changed()
_maybeconstruct(::Unchanged, obj, vals) = obj => Unchanged()
3 changes: 2 additions & 1 deletion test/test_optics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ using Test

@testset "Properties" begin
pt = (x=1, y=2, z=3)
@test (x=0, y=1, z=2) === @set pt |> Properties() -= 1
@test (x=0, y=1, z=2) ===
@set pt |> Properties(pt) -= 1
end

@testset "Elements" begin
Expand Down
20 changes: 20 additions & 0 deletions test/test_queries.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Accessors, ConstructionBase, BenchmarkTools

optic = Accessors.Query(x -> x isa Int)
obj = (7, (a=17.0, b=2.0f0), ("3", 4, 5))
vals = (1.0, 2.0, 3.0, 4.0)

@btime $optic($obj)
@btime set($obj, $optic, $vals)
# Compiles away
@btime modify(x -> 2x, $vals, $optic)


unstable_optic = Accessors.Query(x -> x isa Number && x > 2)

# This is slow
@btime set($x, $unstable_optic, $y)

# This still compiles away
@btime modify(x -> 2x, $vals, $unstable_optic)

0 comments on commit ea5dbc2

Please sign in to comment.