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