diff --git a/Project.toml b/Project.toml index d2917c65..a801e151 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "Accessors" uuid = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697" authors = ["Takafumi Arakaki ", "Jan Weidner and contributors"] -version = "0.1.42" +version = "0.1.43" [deps] CompositionsBase = "a33af91c-f02d-484b-be07-31d278c5ca2b" diff --git a/ext/StaticArraysExt.jl b/ext/StaticArraysExt.jl index ca3913ad..fa616883 100644 --- a/ext/StaticArraysExt.jl +++ b/ext/StaticArraysExt.jl @@ -9,11 +9,13 @@ import Accessors: setindex, delete, insert @inline insert(obj::StaticVector{<:Any,ET}, l::IndexLens, val::T) where {ET,T} = StaticArrays.insert(similar_type(typeof(obj), promote_type(ET, T))(obj), only(l.indices), val) @inline insert(obj::StaticVector{<:Any,T}, l::IndexLens, val::T) where {T} = StaticArrays.insert(obj, only(l.indices), val) -Accessors.set(obj::StaticVector, ::Type{Tuple}, val::Tuple) = constructorof(typeof(obj))(val...) +Accessors.set(obj::StaticVector, ::Type{Tuple}, val::Tuple) = constructorof(typeof(obj))(val) Accessors.set(obj::Tuple, ::Type{<:StaticVector}, val::StaticVector) = Tuple(val) Accessors.getall(obj::StaticArray, ::Elements) = Tuple(obj) -Accessors.setall(obj::StaticArray, ::Elements, vs::AbstractArray) = constructorof(typeof(obj))(vs...) # just for disambiguation -Accessors.setall(obj::StaticArray, ::Elements, vs) = constructorof(typeof(obj))(vs...) +# avoid splatting when possible, only use it with AbstractArray input because StaticArray construction isn't defined otherwise: +Accessors.setall(obj::StaticArray, ::Elements, vs::AbstractArray) = constructorof(typeof(obj))(vs...) +Accessors.setall(obj::StaticArray, ::Elements, vs::StaticArray) = constructorof(typeof(obj))(vs) +Accessors.setall(obj::StaticArray, ::Elements, vs::Tuple) = constructorof(typeof(obj))(vs) end diff --git a/src/functionlenses.jl b/src/functionlenses.jl index f963aa0a..ef8ad0f2 100644 --- a/src/functionlenses.jl +++ b/src/functionlenses.jl @@ -21,8 +21,8 @@ delete(obj::AbstractRange, o::Base.Fix2{typeof(first)}) = obj[begin+o.x:end] delete(obj::AbstractRange, o::Base.Fix2{typeof(last)}) = obj[begin:end-o.x] -set(obj::Tuple, ::typeof(Base.front), val::Tuple) = (val..., last(obj)) -set(obj::Tuple, ::typeof(Base.tail), val::Tuple) = (first(obj), val...) +set(obj::Tuple, ::typeof(Base.front), val::Tuple) = _concat(val, (last(obj),)) +set(obj::Tuple, ::typeof(Base.tail), val::Tuple) = _concat((first(obj),), val) function set(obj, ::typeof(only), val) only(obj) # error check diff --git a/src/getsetall.jl b/src/getsetall.jl index b5e3929d..fc939386 100644 --- a/src/getsetall.jl +++ b/src/getsetall.jl @@ -113,7 +113,7 @@ _getall(obj, optics::Tuple{Any}) = getall(obj, only(optics)) for N in [2:10; :(<: Any)] @eval function _getall(obj, optics::NTuple{$N,Any}) _reduce_concat( - map(getall(obj, last(optics))) do obj + mapspec(getall(obj, last(optics))) do obj _getall(obj, Base.front(optics)) end ) @@ -125,7 +125,7 @@ end _setall(obj, optics::Tuple{Any}, vs) = setall(obj, only(optics), vs) for N in [2:10; :(<: Any)] @eval function _setall(obj, optics::NTuple{$N,Any}, vs) - setall(obj, last(optics), map(getall(obj, last(optics)), vs) do obj, vss + setall(obj, last(optics), mapspec(getall(obj, last(optics)), vs) do obj, vss _setall(obj, Base.front(optics), vss) end) end @@ -134,11 +134,21 @@ end # helper functions -_concat(a::Tuple, b::Tuple) = (a..., b...) +# before using @ntuple, tried: map, broadcast, ntuple function +# all of these suffer from catastrophic performance drop at some point, around 32 elements +@generated _concat(a::NTuple{N,Any}, b::NTuple{M,Any}) where {N,M} = + :(Base.Cartesian.@ntuple $(N + M) i -> i ≤ $N ? a[i] : b[i - $N]) + _concat(a::Tuple, b::AbstractVector) = vcat(collect(a), b) _concat(a::AbstractVector, b::Tuple) = vcat(a, collect(b)) _concat(a::AbstractVector, b::AbstractVector) = vcat(a, b) -_reduce_concat(xs::Tuple) = reduce(_concat, xs; init=()) +@generated function _reduce_concat(xs::NTuple{N,Any}) where {N} + expr = :(()) + for i in 1:N + expr = :(_concat($expr, xs[$i])) + end + expr +end _reduce_concat(xs::AbstractVector) = reduce(append!, xs; init=eltype(eltype(xs))[]) # fast path: _reduce_concat(xs::Tuple{AbstractVector, Vararg{AbstractVector}}) = reduce(vcat, xs) @@ -150,7 +160,7 @@ _staticlength(x::AbstractVector) = length(x) getall_lengths(obj, optics::Tuple{Any}) = _staticlength(getall(obj, only(optics))) for N in [2:10; :(<: Any)] @eval getall_lengths(obj, optics::NTuple{$N,Any}) = - map(getall(obj, last(optics))) do o + mapspec(getall(obj, last(optics))) do o getall_lengths(o, Base.front(optics)) end end @@ -195,7 +205,7 @@ for i in 2:10 elems, elemstail = splitelems(vs, n) reshead = to_nested_shape(elems, lss, $(Val(i - 1))) restail = to_nested_shape(elemstail, Base.tail(ls), $(Val(i))) - return (reshead, restail...) + return _concat((reshead,), restail) end @eval function to_nested_shape(vs, ls::Vector, ::Val{$i}) @@ -209,3 +219,10 @@ for i in 2:10 end end end + +# like Base map(), but always specializes for tuples, no matter the heuristics +# Accessors are notoriously heavy for inference, Julia compiler often stops specializing too early, and map() ends up among the first sactifices +# note: tests may pass with regular map(), but still be very careful removing this optimization! +mapspec(f, xs...) = map(f, xs...) +@generated mapspec(f, t::NTuple{N,Any}) where {N} = :(Base.Cartesian.@ntuple $N i -> f(t[i])) +@generated mapspec(f, t1::NTuple{N,Any}, t2::NTuple{N,Any}) where {N} = :(Base.Cartesian.@ntuple $N i -> f(t1[i], t2[i]))