Skip to content

Commit

Permalink
Support index- and key-based set
Browse files Browse the repository at this point in the history
  • Loading branch information
tkf committed May 27, 2019
1 parent 50c0be6 commit c474e71
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 7 deletions.
1 change: 1 addition & 0 deletions src/Kaleido.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ using Setfield
using Requires

include("base.jl")
include("batchsetters.jl")
include("batchlenses.jl")
include("multilens.jl")
include("flatlens.jl")
Expand Down
19 changes: 12 additions & 7 deletions src/batchlenses.jl
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,19 @@ lenstypenameof(::BatchLens{<:Any, PROPERTY, valueacc}) where valueacc =
INDEX => :IndexBatchLens,
)[valueacc]

Setfield.set(obj, ::BatchLens{names, PROPERTY, INDEX}, val) where names =
setproperties(obj, NamedTuple{names}(val))
setter(objacc::Accessor) = setter(Val(objacc))
setter(::Val{INDEX}) = setindices
setter(::Val{KEY}) = setkeys
setter(::Val{PROPERTY}) = setproperties

Setfield.set(obj, ::BatchLens{names, PROPERTY, KEY}, val) where names =
setproperties(obj, NamedTuple{names}(map(n -> val[n], names)))
Setfield.set(obj, ::BatchLens{names, objacc, INDEX}, val) where {names, objacc} =
setter(objacc)(obj, NamedTuple{names}(Tuple(val)))

Setfield.set(obj, ::BatchLens{names, PROPERTY, PROPERTY}, val) where names =
setproperties(obj, NamedTuple{names}(map(n -> getproperty(val, n), names)))
Setfield.set(obj, ::BatchLens{names, objacc, KEY}, val) where {names, objacc} =
setter(objacc)(obj, NamedTuple{names}(map(n -> val[n], names)))

Setfield.set(obj, ::BatchLens{names, objacc, PROPERTY}, val) where {names, objacc} =
setter(objacc)(obj, NamedTuple{names}(map(n -> getproperty(val, n), names)))

_get(::Val{INDEX}, obj, (i, name)) = getindex(obj, i)
_get(::Val{KEY}, obj, (i, name)) = getindex(obj, name)
Expand All @@ -88,5 +93,5 @@ Setfield.get(obj, lens::Union{BatchLens{names, <:Any, KEY},
BatchLens{<:Any, objacc, valueacc}(names::Vararg{Symbol}) where {objacc, valueacc} =
BatchLens{names, objacc, valueacc}()

Base.show(io::IO, lens::BatchLens{names}) where names =
Base.show(io::IO, lens::BatchLens{names, PROPERTY}) where names =
print_apply(io, Prefixed(prefixof(BatchLens), lenstypenameof(lens)), names)
39 changes: 39 additions & 0 deletions src/batchsetters.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
abstract type Mutability end
struct Immutable <: Mutability end
struct Mutable <: Mutability end

Mutability(::Any) = Mutable()
Mutability(::Union{Tuple, NamedTuple}) = Immutable()

setindices(obj, batch) = setindices(Mutability(obj), obj, batch)
setkeys(obj, batch) = setkeys(Mutability(obj), obj, batch)

_eachindex(x) = eachindex(x)
_eachindex(x::NamedTuple) = Base.OneTo(length(x))

function setindices(::Mutable, obj, batch)
out = copy(obj)
for i in _eachindex(batch)
out[i] = batch[i]
end
return out
end

setindices(::Immutable, obj, batch) =
_mapfoldl(i -> (i, batch[i]),
(obj′, (i, v)) -> set(obj′, (@lens _[i]), v),
Tuple(_eachindex(batch)),
obj)

function setkeys(::Mutable, obj, batch)
out = copy(obj)
for (k, v) in pairs(batch)
out[k] = v
end
return out
end

setkeys(::Immutable, obj, batch) =
_foldl(Tuple(pairs(batch)), obj) do obj, (k, v)
set(obj, (@lens _[k]), v)
end
60 changes: 60 additions & 0 deletions test/test_batchlenses.jl
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,64 @@ end
end
end

const Associative = Union{AbstractDict, NamedTuple}

eq(x, y) = x == y
eq(x::Associative, y::Associative) =
sort(collect(keys(x))) == sort(collect(keys(y))) &&
all(eq.(getindex.(Ref(x), keys(x)),
getindex.(Ref(y), keys(x))))
eq(x::Tuple, y::NamedTuple) = x == Tuple(y)
eq(x::NamedTuple, y::Tuple) = Tuple(x) == y

@testset "laws" begin
@testset "$obj" for (obj, objacc_list) in [
((a=1, b=2, c=3, d=4), [PROPERTY]),
((1, 2, 3, 4), [INDEX]),
(Dict(:a=>1, :b=>2, :c=>3, :d=>4), [KEY]),
]
@testset "BatchLens{_, $objacc, $valueacc}" for
objacc in objacc_list,
(valueacc, val_list) in [
(INDEX, [
(1, 2, 3),
(c=4, b=5, a=6),
]),
(KEY, [
(a=1, b=2, c=3),
(c=4, b=5, a=6),
Dict(:a=>7, :b=>8, :c=>9),
]),
]

lens = BatchLens{(:a, :b, :c), objacc, valueacc}()

@testset "You get what you set." begin
@testset for val in val_list
@debug(
"eq(get(set(obj, lens, val), lens), val)",
obj,
lens,
lhs = get(set(obj, lens, val), lens),
rhs = val,
)
@test eq(get(set(obj, lens, val), lens), val)
end
end

@testset "Setting what was already there changes nothing." begin
@test set(obj, lens, get(obj, lens)) == obj
end

@testset "The last set wins." begin
val1 = val_list[1]
@testset for val2 in val_list
@test set(set(obj, lens, val1), lens, val2) ==
set(obj, lens, val2)
end
end
end
end
end

end # module

0 comments on commit c474e71

Please sign in to comment.