1
+ """
2
+ Functors.functor(x) = functor(typeof(x), x)
3
+
4
+ Returns a tuple containing, first, a `NamedTuple` of the children of `x`
5
+ (typically its fields), and second, a reconstruction funciton.
6
+ This controls the behaviour of [`fmap`](@ref).
7
+
8
+ Methods should be added to `functor(::Type{T}, x)` for custom types,
9
+ usually using the macro [@functor](@ref).
10
+ """
1
11
functor (T, x) = (), _ -> x
2
12
functor (x) = functor (typeof (x), x)
3
13
@@ -29,6 +39,47 @@ function functorm(T, fs = nothing)
29
39
:(makefunctor (@__MODULE__ , $ (esc (T)), $ (fs... )))
30
40
end
31
41
42
+ """
43
+ @functor T
44
+ @functor T (x,)
45
+
46
+ Adds methods to [`functor`](@ref) allowing recursion into objects of type `T`,
47
+ and reconstruction. Assumes that `T` has a constructor accepting all of its fields,
48
+ which is true unless you have provided an inner constructor which does not.
49
+
50
+ By default all fields of `T` are considered [children](@ref);
51
+ this can be restricted be restructed by providing a tuple of field names.
52
+
53
+ # Examples
54
+ ```jldoctest
55
+ julia> struct Foo; x; y; end
56
+
57
+ julia> @functor Foo
58
+
59
+ julia> Functors.children(Foo(1,2))
60
+ (x = 1, y = 2)
61
+
62
+ julia> _, re = Functors.functor(Foo(1,2));
63
+
64
+ julia> re((10, 20))
65
+ Foo(10, 20)
66
+
67
+ julia> struct TwoThirds a; b; c; end
68
+
69
+ julia> @functor TwoThirds (a, c)
70
+
71
+ julia> ch2, re3 = Functors.functor(TwoThirds(10,20,30));
72
+
73
+ julia> ch2
74
+ (a = 10, c = 30)
75
+
76
+ julia> re3(("ten", "thirty"))
77
+ TwoThirds("ten", 20, "thirty")
78
+
79
+ julia> fmap(x -> 10x, TwoThirds(Foo(1,2), Foo(3,4), 56))
80
+ TwoThirds(Foo(10, 20), Foo(3, 4), 560)
81
+ ```
82
+ """
32
83
macro functor (args... )
33
84
functorm (args... )
34
85
end
@@ -61,14 +112,35 @@ macro flexiblefunctor(args...)
61
112
end
62
113
63
114
"""
64
- isleaf(x)
115
+ Functors. isleaf(x)
65
116
66
117
Return true if `x` has no [`children`](@ref) according to [`functor`](@ref).
118
+
119
+ # Examples
120
+ ```jldoctest
121
+ julia> Functors.isleaf(1)
122
+ true
123
+
124
+ julia> Functors.isleaf([2, 3, 4])
125
+ true
126
+
127
+ julia> Functors.isleaf(["five", [6, 7]])
128
+ false
129
+
130
+ julia> Functors.isleaf([])
131
+ false
132
+
133
+ julia> Functors.isleaf((8, 9))
134
+ false
135
+
136
+ julia> Functors.isleaf(())
137
+ true
138
+ ```
67
139
"""
68
140
isleaf (x) = children (x) === ()
69
141
70
142
"""
71
- children(x)
143
+ Functors. children(x)
72
144
73
145
Return the children of `x` as defined by [`functor`](@ref).
74
146
Equivalent to `functor(x)[1]`.
@@ -100,18 +172,55 @@ end
100
172
_default_walk (f, :: Nothing , :: Nothing ) = nothing
101
173
102
174
"""
103
- fmap(f, x; exclude = isleaf, walk = Functors._default_walk)
104
-
105
- A structure and type preserving `map` that works for all [`functor`](@ref)s.
175
+ fmap(f, x; exclude = Functors.isleaf, walk = Functors._default_walk)
106
176
107
- By default, traverses `x` recursively using [`functor`](@ref)
108
- and transforms every leaf node identified by `exclude` with `f`.
177
+ A structure and type preserving `map`.
109
178
110
- For advanced customization of the traversal behaviour, pass a custom `walk` function of the form `(f', xs) -> ...`.
111
- This function walks (maps) over `xs` calling the continuation `f'` to continue traversal .
179
+ By default it transforms every leaf node (identified by `exclude`, default [`isleaf`](@ref))
180
+ by applying `f`, and otherwise traverses `x` recursively using [`functor`](@ref) .
112
181
113
182
# Examples
114
183
```jldoctest
184
+ julia> fmap(string, (x=1, y=(2, 3)))
185
+ (x = "1", y = ("2", "3"))
186
+
187
+ julia> nt = (a = [1,2], b = [23, (45,), (x=6//7, y=())], c = [8,9]);
188
+
189
+ julia> fmap(println, nt)
190
+ [1, 2]
191
+ 23
192
+ 45
193
+ 6//7
194
+ ()
195
+ [8, 9]
196
+ (a = nothing, b = Any[nothing, (nothing,), (x = nothing, y = nothing)], c = nothing)
197
+
198
+ julia> fmap(println, nt; exclude = x -> x isa Array)
199
+ [1, 2]
200
+ Any[23, (45,), (x = 6//7, y = ())]
201
+ [8, 9]
202
+ (a = nothing, b = nothing, c = nothing)
203
+
204
+ julia> twice = [1, 2];
205
+
206
+ julia> fmap(println, (i = twice, ii = 34, iii = [5, 6], iv = (twice, 34), v = 34.0))
207
+ [1, 2]
208
+ 34
209
+ [5, 6]
210
+ 34.0
211
+ (i = nothing, ii = nothing, iii = nothing, iv = (nothing, nothing), v = nothing)
212
+ ```
213
+
214
+ If the same node (same according to `===`) appears more than once,
215
+ it will only be handled once, and only be transformed once with `f`.
216
+ Thus the result will also have this relationship.
217
+
218
+ By default, `Tuple`s, `NamedTuple`s, and some other container-like types in Base have
219
+ children to recurse into. Arrays of numbers do not.
220
+ To enable recursion into new types, you must provide a method of [`functor`](@ref),
221
+ which can be done using the macro [`@functor`](@ref):
222
+
223
+ ```jldoctest withfoo
115
224
julia> struct Foo; x; y; end
116
225
117
226
julia> @functor Foo
@@ -120,19 +229,27 @@ julia> struct Bar; x; end
120
229
121
230
julia> @functor Bar
122
231
123
- julia> m = Foo(Bar([1,2,3]), (4, 5));
232
+ julia> m = Foo(Bar([1,2,3]), (4, 5, Bar(Foo(6, 7)) ));
124
233
125
- julia> fmap(x -> 2x , m)
126
- Foo(Bar([2, 4, 6 ]), (8, 10 ))
234
+ julia> fmap(x -> 10x , m)
235
+ Foo(Bar([10, 20, 30 ]), (40, 50, Bar(Foo(60, 70)) ))
127
236
128
237
julia> fmap(string, m)
129
- Foo(Bar("[1, 2, 3]"), ("4", "5"))
238
+ Foo(Bar("[1, 2, 3]"), ("4", "5", Bar(Foo("6", "7")) ))
130
239
131
240
julia> fmap(string, m, exclude = v -> v isa Bar)
132
- Foo("Bar([1, 2, 3])", (4, 5))
241
+ Foo("Bar([1, 2, 3])", (4, 5, "Bar(Foo(6, 7))"))
242
+ ```
243
+
244
+ To recurse into custom types without reconstructing them afterwards,
245
+ use [`fmapstructure`](@ref).
246
+
247
+ For advanced customization of the traversal behaviour, pass a custom `walk` function of the form `(f', xs) -> ...`.
248
+ This function walks (maps) over `xs` calling the continuation `f'` to continue traversal.
133
249
134
- julia> fmap(x -> 2x, m, walk=(f, x) -> x isa Bar ? x : Functors._default_walk(f, x))
135
- Foo(Bar([1, 2, 3]), (8, 10))
250
+ ```jldoctest withfoo
251
+ julia> fmap(x -> 10x, m, walk=(f, x) -> x isa Bar ? x : Functors._default_walk(f, x))
252
+ Foo(Bar([1, 2, 3]), (40, 50, Bar(Foo(6, 7))))
136
253
```
137
254
"""
138
255
function fmap (f, x; exclude = isleaf, walk = _default_walk, cache = IdDict ())
146
263
"""
147
264
fmapstructure(f, x; exclude = isleaf)
148
265
149
- Like [`fmap`](@ref), but doesn't preserve the type of custom structs. Instead, it returns a (potentially nested) `NamedTuple`.
266
+ Like [`fmap`](@ref), but doesn't preserve the type of custom structs.
267
+ Instead, it returns a `NamedTuple` (or a `Tuple`, or an array),
268
+ or a nested set of these.
150
269
151
270
Useful for when the output must not contain custom structs.
152
271
@@ -156,10 +275,19 @@ julia> struct Foo; x; y; end
156
275
157
276
julia> @functor Foo
158
277
159
- julia> m = Foo([1,2,3], ( 4, 5) );
278
+ julia> m = Foo([1,2,3], [ 4, (5, 6), Foo(7, 8)] );
160
279
161
280
julia> fmapstructure(x -> 2x, m)
162
- (x = [2, 4, 6], y = (8, 10))
281
+ (x = [2, 4, 6], y = Any[8, (10, 12), (x = 14, y = 16)])
282
+
283
+ julia> fmapstructure(println, m)
284
+ [1, 2, 3]
285
+ 4
286
+ 5
287
+ 6
288
+ 7
289
+ 8
290
+ (x = nothing, y = Any[nothing, (nothing, nothing), (x = nothing, y = nothing)])
163
291
```
164
292
"""
165
293
fmapstructure (f, x; kwargs... ) = fmap (f, x; walk = (f, x) -> map (f, children (x)), kwargs... )
0 commit comments