@@ -5,4 +5,260 @@ export @functor, @flexiblefunctor, fmap, fmapstructure, fcollect
5
5
include (" functor.jl" )
6
6
include (" base.jl" )
7
7
8
+ """
9
+ Functors.functor(x) = functor(typeof(x), x)
10
+
11
+ Returns a tuple containing, first, a `NamedTuple` of the children of `x`
12
+ (typically its fields), and second, a reconstruction funciton.
13
+ This controls the behaviour of [`fmap`](@ref).
14
+
15
+ Methods should be added to `functor(::Type{T}, x)` for custom types,
16
+ usually using the macro [@functor](@ref).
17
+ """
18
+ functor
19
+
20
+ """
21
+ @functor T
22
+ @functor T (x,)
23
+
24
+ Adds methods to [`functor`](@ref) allowing recursion into objects of type `T`,
25
+ and reconstruction. Assumes that `T` has a constructor accepting all of its fields,
26
+ which is true unless you have provided an inner constructor which does not.
27
+
28
+ By default all fields of `T` are considered [children](@ref);
29
+ this can be restricted be restructed by providing a tuple of field names.
30
+
31
+ # Examples
32
+ ```jldoctest
33
+ julia> struct Foo; x; y; end
34
+
35
+ julia> @functor Foo
36
+
37
+ julia> Functors.children(Foo(1,2))
38
+ (x = 1, y = 2)
39
+
40
+ julia> _, re = Functors.functor(Foo(1,2));
41
+
42
+ julia> re((10, 20))
43
+ Foo(10, 20)
44
+
45
+ julia> struct TwoThirds a; b; c; end
46
+
47
+ julia> @functor TwoThirds (a, c)
48
+
49
+ julia> ch2, re3 = Functors.functor(TwoThirds(10,20,30));
50
+
51
+ julia> ch2
52
+ (a = 10, c = 30)
53
+
54
+ julia> re3(("ten", "thirty"))
55
+ TwoThirds("ten", 20, "thirty")
56
+
57
+ julia> fmap(x -> 10x, TwoThirds(Foo(1,2), Foo(3,4), 56))
58
+ TwoThirds(Foo(10, 20), Foo(3, 4), 560)
59
+ ```
60
+ """
61
+ var"@functor"
62
+
63
+ """
64
+ Functors.isleaf(x)
65
+
66
+ Return true if `x` has no [`children`](@ref) according to [`functor`](@ref).
67
+
68
+ # Examples
69
+ ```jldoctest
70
+ julia> Functors.isleaf(1)
71
+ true
72
+
73
+ julia> Functors.isleaf([2, 3, 4])
74
+ true
75
+
76
+ julia> Functors.isleaf(["five", [6, 7]])
77
+ false
78
+
79
+ julia> Functors.isleaf([])
80
+ false
81
+
82
+ julia> Functors.isleaf((8, 9))
83
+ false
84
+
85
+ julia> Functors.isleaf(())
86
+ true
87
+ ```
88
+ """
89
+ isleaf
90
+
91
+ """
92
+ Functors.children(x)
93
+
94
+ Return the children of `x` as defined by [`functor`](@ref).
95
+ Equivalent to `functor(x)[1]`.
96
+ """
97
+ children
98
+
99
+ """
100
+ fmap(f, x; exclude = Functors.isleaf, walk = Functors._default_walk)
101
+
102
+ A structure and type preserving `map`.
103
+
104
+ By default it transforms every leaf node (identified by `exclude`, default [`isleaf`](@ref))
105
+ by applying `f`, and otherwise traverses `x` recursively using [`functor`](@ref).
106
+
107
+ # Examples
108
+ ```jldoctest
109
+ julia> fmap(string, (x=1, y=(2, 3)))
110
+ (x = "1", y = ("2", "3"))
111
+
112
+ julia> nt = (a = [1,2], b = [23, (45,), (x=6//7, y=())], c = [8,9]);
113
+
114
+ julia> fmap(println, nt)
115
+ [1, 2]
116
+ 23
117
+ 45
118
+ 6//7
119
+ ()
120
+ [8, 9]
121
+ (a = nothing, b = Any[nothing, (nothing,), (x = nothing, y = nothing)], c = nothing)
122
+
123
+ julia> fmap(println, nt; exclude = x -> x isa Array)
124
+ [1, 2]
125
+ Any[23, (45,), (x = 6//7, y = ())]
126
+ [8, 9]
127
+ (a = nothing, b = nothing, c = nothing)
128
+
129
+ julia> twice = [1, 2];
130
+
131
+ julia> fmap(println, (i = twice, ii = 34, iii = [5, 6], iv = (twice, 34), v = 34.0))
132
+ [1, 2]
133
+ 34
134
+ [5, 6]
135
+ 34.0
136
+ (i = nothing, ii = nothing, iii = nothing, iv = (nothing, nothing), v = nothing)
137
+ ```
138
+
139
+ If the same node (same according to `===`) appears more than once,
140
+ it will only be handled once, and only be transformed once with `f`.
141
+ Thus the result will also have this relationship.
142
+
143
+ By default, `Tuple`s, `NamedTuple`s, and some other container-like types in Base have
144
+ children to recurse into. Arrays of numbers do not.
145
+ To enable recursion into new types, you must provide a method of [`functor`](@ref),
146
+ which can be done using the macro [`@functor`](@ref):
147
+
148
+ ```jldoctest withfoo
149
+ julia> struct Foo; x; y; end
150
+
151
+ julia> @functor Foo
152
+
153
+ julia> struct Bar; x; end
154
+
155
+ julia> @functor Bar
156
+
157
+ julia> m = Foo(Bar([1,2,3]), (4, 5, Bar(Foo(6, 7))));
158
+
159
+ julia> fmap(x -> 10x, m)
160
+ Foo(Bar([10, 20, 30]), (40, 50, Bar(Foo(60, 70))))
161
+
162
+ julia> fmap(string, m)
163
+ Foo(Bar("[1, 2, 3]"), ("4", "5", Bar(Foo("6", "7"))))
164
+
165
+ julia> fmap(string, m, exclude = v -> v isa Bar)
166
+ Foo("Bar([1, 2, 3])", (4, 5, "Bar(Foo(6, 7))"))
167
+ ```
168
+
169
+ To recurse into custom types without reconstructing them afterwards,
170
+ use [`fmapstructure`](@ref).
171
+
172
+ For advanced customization of the traversal behaviour, pass a custom `walk` function of the form `(f', xs) -> ...`.
173
+ This function walks (maps) over `xs` calling the continuation `f'` to continue traversal.
174
+
175
+ ```jldoctest withfoo
176
+ julia> fmap(x -> 10x, m, walk=(f, x) -> x isa Bar ? x : Functors._default_walk(f, x))
177
+ Foo(Bar([1, 2, 3]), (40, 50, Bar(Foo(6, 7))))
178
+ ```
179
+ """
180
+ fmap
181
+
182
+ """
183
+ fmapstructure(f, x; exclude = isleaf)
184
+
185
+ Like [`fmap`](@ref), but doesn't preserve the type of custom structs.
186
+ Instead, it returns a `NamedTuple` (or a `Tuple`, or an array),
187
+ or a nested set of these.
188
+
189
+ Useful for when the output must not contain custom structs.
190
+
191
+ # Examples
192
+ ```jldoctest
193
+ julia> struct Foo; x; y; end
194
+
195
+ julia> @functor Foo
196
+
197
+ julia> m = Foo([1,2,3], [4, (5, 6), Foo(7, 8)]);
198
+
199
+ julia> fmapstructure(x -> 2x, m)
200
+ (x = [2, 4, 6], y = Any[8, (10, 12), (x = 14, y = 16)])
201
+
202
+ julia> fmapstructure(println, m)
203
+ [1, 2, 3]
204
+ 4
205
+ 5
206
+ 6
207
+ 7
208
+ 8
209
+ (x = nothing, y = Any[nothing, (nothing, nothing), (x = nothing, y = nothing)])
210
+ ```
211
+ """
212
+ fmapstructure
213
+
214
+ """
215
+ fcollect(x; exclude = v -> false)
216
+
217
+ Traverse `x` by recursing each child of `x` as defined by [`functor`](@ref)
218
+ and collecting the results into a flat array, ordered by a breadth-first
219
+ traversal of `x`, respecting the iteration order of `children` calls.
220
+
221
+ Doesn't recurse inside branches rooted at nodes `v`
222
+ for which `exclude(v) == true`.
223
+ In such cases, the root `v` is also excluded from the result.
224
+ By default, `exclude` always yields `false`.
225
+
226
+ See also [`children`](@ref).
227
+
228
+ # Examples
229
+
230
+ ```jldoctest
231
+ julia> struct Foo; x; y; end
232
+
233
+ julia> @functor Foo
234
+
235
+ julia> struct Bar; x; end
236
+
237
+ julia> @functor Bar
238
+
239
+ julia> struct NoChildren; x; y; end
240
+
241
+ julia> m = Foo(Bar([1,2,3]), NoChildren(:a, :b))
242
+ Foo(Bar([1, 2, 3]), NoChildren(:a, :b))
243
+
244
+ julia> fcollect(m)
245
+ 4-element Vector{Any}:
246
+ Foo(Bar([1, 2, 3]), NoChildren(:a, :b))
247
+ Bar([1, 2, 3])
248
+ [1, 2, 3]
249
+ NoChildren(:a, :b)
250
+
251
+ julia> fcollect(m, exclude = v -> v isa Bar)
252
+ 2-element Vector{Any}:
253
+ Foo(Bar([1, 2, 3]), NoChildren(:a, :b))
254
+ NoChildren(:a, :b)
255
+
256
+ julia> fcollect(m, exclude = v -> Functors.isleaf(v))
257
+ 2-element Vector{Any}:
258
+ Foo(Bar([1, 2, 3]), NoChildren(:a, :b))
259
+ Bar([1, 2, 3])
260
+ ```
261
+ """
262
+ fcollect
263
+
8
264
end # module
0 commit comments