Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "Accessors"
uuid = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697"
authors = ["Takafumi Arakaki <aka.tkf@gmail.com>", "Jan Weidner <jw3126@gmail.com> and contributors"]
version = "0.1.42"
version = "0.1.43"

[deps]
CompositionsBase = "a33af91c-f02d-484b-be07-31d278c5ca2b"
Expand Down
8 changes: 5 additions & 3 deletions ext/StaticArraysExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 2 additions & 2 deletions src/functionlenses.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 23 additions & 6 deletions src/getsetall.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand All @@ -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
Expand All @@ -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])
Comment on lines +139 to +140

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no reliably fast way to do this in Base?

@aplavin aplavin Dec 2, 2025 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried:

  • map
  • broadcast
  • ntuple the function

All of these suffer from catastrophic performance drop at some point. Seems like it's around 32 elements returned by getall in total, the unrolling threshold for tuples in Base.

Added a comment.


_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)
Expand All @@ -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
Expand Down Expand Up @@ -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})
Expand All @@ -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]))
Loading