3
3
4
4
Extract all parts of `obj` that are selected by `optic`.
5
5
Returns a flat `Tuple` of values, or an `AbstractVector` if the selected parts contain arrays.
6
- This function is experimental and we might change the precise output container in future.
6
+
7
+ This function is experimental and we might change the precise output container in the future.
8
+
9
+ See also [`setall`](@ref).
7
10
8
11
9
12
```jldoctest
@@ -20,6 +23,34 @@ julia> getall(obj, @optic _ |> Elements() |> last)
20
23
"""
21
24
function getall end
22
25
26
+ """
27
+ setall(obj, optic, values)
28
+
29
+ Replace a part of `obj` that is selected by `optic` with `values`.
30
+ The `values` collection should have the same number of elements as selected by `optic`.
31
+
32
+ This function is experimental and might change in the future.
33
+
34
+ See also [`getall`](@ref), [`set`](@ref). The former is dual to `setall`:
35
+
36
+ ```jldoctest
37
+ julia> using Accessors
38
+
39
+ julia> obj = (a=1, b=(2, 3));
40
+
41
+ julia> optic = @optic _ |> Elements() |> last;
42
+
43
+ julia> getall(obj, optic)
44
+ (1, 3)
45
+
46
+ julia> setall(obj, optic, (4, 5))
47
+ (a = 4, b = (2, 5))
48
+ ```
49
+ """
50
+ function setall end
51
+
52
+ # implementations for individual noncomposite optics
53
+
23
54
getall (obj:: Union{Tuple, AbstractVector} , :: Elements ) = obj
24
55
getall (obj:: Union{NamedTuple} , :: Elements ) = values (obj)
25
56
getall (obj:: AbstractArray , :: Elements ) = vec (obj)
@@ -29,17 +60,63 @@ getall(obj, ::Properties) = getproperties(obj) |> values
29
60
getall (obj, o:: If ) = o. modify_condition (obj) ? (obj,) : ()
30
61
getall (obj, f) = (f (obj),)
31
62
63
+ function setall (obj, :: Properties , vs)
64
+ names = propertynames (obj)
65
+ setproperties (obj, NamedTuple {names} (NTuple {length(names)} (vs)))
66
+ end
67
+ setall (obj:: NamedTuple{NS} , :: Elements , vs) where {NS} = NamedTuple {NS} (NTuple {length(NS)} (vs))
68
+ setall (obj:: NTuple{N, Any} , :: Elements , vs) where {N} = (@assert length (vs) == N; NTuple {N} (vs))
69
+ setall (obj:: AbstractArray , :: Elements , vs:: AbstractArray ) = (@assert length (obj) == length (vs); reshape (vs, size (obj)))
70
+ setall (obj:: AbstractArray , :: Elements , vs) = setall (obj, Elements (), collect (vs))
71
+ setall (obj, o:: If , vs) = error (" Not supported" )
72
+ setall (obj, o, vs) = set (obj, o, only (vs))
73
+
74
+
75
+ # implementations for composite optics
76
+
77
+ # A straightforward recursive approach doesn't actually infer,
78
+ # see https://github.com/JuliaObjects/Accessors.jl/pull/64 and https://github.com/JuliaObjects/Accessors.jl/pull/68.
79
+ # Instead, we need to generate separate functions for each recursion level.
32
80
33
- # A recursive implementation of getall doesn't actually infer,
34
- # see https://github.com/JuliaObjects/Accessors.jl/pull/64.
35
- # Instead, we need to generate unrolled code explicitly.
36
81
function getall (obj, optic:: ComposedFunction )
37
82
N = length (decompose (optic))
38
- _GetAll {N} ()(obj, optic)
83
+ _getall (obj, optic, Val (N))
84
+ end
85
+
86
+ function setall (obj, optic:: ComposedFunction , vs)
87
+ N = length (decompose (optic))
88
+ vss = to_nested_shape (vs, Val (getall_lengths (obj, optic, Val (N))), Val (N))
89
+ _setall (obj, optic, vss, Val (N))
90
+ end
91
+
92
+
93
+ # _getall: the actual workhorse for getall
94
+ _getall (_, _, :: Val{N} ) where {N} = error (" Too many chained optics: $N is not supported for now. See also https://github.com/JuliaObjects/Accessors.jl/pull/64." )
95
+ _getall (obj, optic, :: Val{1} ) = getall (obj, optic)
96
+ for i in 2 : 10
97
+ @eval function _getall (obj, optic, :: Val{$i} )
98
+ _reduce_concat (
99
+ map (getall (obj, optic. inner)) do obj
100
+ _getall (obj, optic. outer, Val ($ (i- 1 )))
101
+ end
102
+ )
103
+ end
104
+ end
105
+
106
+ # _setall: the actual workhorse for setall
107
+ # takes values as a nested tuple with proper leaf lengths, prepared in setall above
108
+ _setall (_, _, _, :: Val{N} ) where {N} = error (" Too many chained optics: $N is not supported for now. See also https://github.com/JuliaObjects/Accessors.jl/pull/68." )
109
+ _setall (obj, optic, vs, :: Val{1} ) = setall (obj, optic, vs)
110
+ for i in 2 : 10
111
+ @eval function _setall (obj, optic, vs, :: Val{$i} )
112
+ setall (obj, optic. inner, map (getall (obj, optic. inner), vs) do obj, vss
113
+ _setall (obj, optic. outer, vss, Val ($ (i - 1 )))
114
+ end )
115
+ end
39
116
end
40
117
41
- struct _GetAll{N} end
42
- ( :: _GetAll{N} )(_) where {N} = error ( " Too many chained optics: $N is not supported for now. See also https://github.com/JuliaObjects/Accessors.jl/pull/64. " )
118
+
119
+ # helper functions
43
120
44
121
_concat (a:: Tuple , b:: Tuple ) = (a... , b... )
45
122
_concat (a:: Tuple , b:: AbstractVector ) = vcat (collect (a), b)
@@ -51,26 +128,48 @@ _reduce_concat(xs::AbstractVector) = reduce(append!, xs; init=eltype(eltype(xs))
51
128
_reduce_concat (xs:: Tuple{AbstractVector, Vararg{AbstractVector}} ) = reduce (vcat, xs)
52
129
_reduce_concat (xs:: AbstractVector{<:AbstractVector} ) = reduce (vcat, xs)
53
130
54
- function _generate_getall (N:: Int )
55
- syms = [Symbol (:f_ , i) for i in 1 : N]
56
-
57
- expr = :( getall (obj, $ (syms[end ])) )
58
- for s in syms[1 : end - 1 ] |> reverse
59
- expr = :(
60
- _reduce_concat (
61
- map (getall (obj, $ (s))) do obj
62
- $ expr
63
- end
64
- )
65
- )
66
- end
131
+ _staticlength (:: NTuple{N, <:Any} ) where {N} = Val (N)
132
+ _staticlength (x:: AbstractVector ) = length (x)
67
133
68
- :(function (:: _GetAll{$N} )(obj, optic)
69
- ($ (syms... ),) = deopcompose (optic)
70
- $ expr
71
- end )
134
+ getall_lengths (obj, optic, :: Val{1} ) = _staticlength (getall (obj, optic))
135
+ for i in 2 : 10
136
+ @eval function getall_lengths (obj, optic, :: Val{$i} )
137
+ # convert to Tuple: vectors cannot be put into Val
138
+ map (getall (obj, optic. inner) |> Tuple) do o
139
+ getall_lengths (o, optic. outer, Val ($ (i - 1 )))
140
+ end
141
+ end
72
142
end
73
143
144
+ _val (N:: Int ) = N
145
+ _val (:: Val{N} ) where {N} = N
146
+
147
+ nestedsum (ls:: Int ) = ls
148
+ nestedsum (ls:: Val ) = ls
149
+ nestedsum (ls:: Tuple ) = sum (_val ∘ nestedsum, ls)
150
+
151
+ # to_nested_shape() definition uses both @eval and @generated
152
+ #
153
+ # @eval is needed because the code for different recursion depths should be different for inference,
154
+ # not the same method with different parameters.
155
+ #
156
+ # @generated is used to unpack target lengths from the second argument at compile time to make to_nested_shape() as cheap as possible.
157
+ #
158
+ # Note: to_nested_shape() only operates on plain Julia types and won't be affected by user lens definition, unlike setall for example.
159
+ # That's why it's safe to make it @generated.
160
+ to_nested_shape (vs, :: Val{LS} , :: Val{1} ) where {LS} = (@assert length (vs) == _val (LS); vs)
74
161
for i in 2 : 10
75
- eval (_generate_getall (i))
162
+ @eval @generated function to_nested_shape (vs, ls:: Val{LS} , :: Val{$i} ) where {LS}
163
+ vi = 1
164
+ subs = map (LS) do lss
165
+ n = nestedsum (lss)
166
+ elems = map (vi: vi+ _val (n)- 1 ) do j
167
+ :( vs[$ j] )
168
+ end
169
+ res = :( to_nested_shape (($ (elems... ),), $ (Val (lss)), $ (Val ($ (i - 1 )))) )
170
+ vi += _val (n)
171
+ res
172
+ end
173
+ :( ($ (subs... ),) )
174
+ end
76
175
end
0 commit comments