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+ """
111functor (T, x) = (), _ -> x
212functor (x) = functor (typeof (x), x)
313
@@ -29,6 +39,47 @@ function functorm(T, fs = nothing)
2939 :(makefunctor (@__MODULE__ , $ (esc (T)), $ (fs... )))
3040end
3141
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+ """
3283macro functor (args... )
3384 functorm (args... )
3485end
@@ -61,14 +112,35 @@ macro flexiblefunctor(args...)
61112end
62113
63114"""
64- isleaf(x)
115+ Functors. isleaf(x)
65116
66117Return 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+ ```
67139"""
68140isleaf (x) = children (x) === ()
69141
70142"""
71- children(x)
143+ Functors. children(x)
72144
73145Return the children of `x` as defined by [`functor`](@ref).
74146Equivalent to `functor(x)[1]`.
@@ -100,18 +172,55 @@ end
100172_default_walk (f, :: Nothing , :: Nothing ) = nothing
101173
102174"""
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)
106176
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`.
109178
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) .
112181
113182# Examples
114183```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
115224julia> struct Foo; x; y; end
116225
117226julia> @functor Foo
@@ -120,19 +229,27 @@ julia> struct Bar; x; end
120229
121230julia> @functor Bar
122231
123- julia> m = Foo(Bar([1,2,3]), (4, 5));
232+ julia> m = Foo(Bar([1,2,3]), (4, 5, Bar(Foo(6, 7)) ));
124233
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)) ))
127236
128237julia> fmap(string, m)
129- Foo(Bar("[1, 2, 3]"), ("4", "5"))
238+ Foo(Bar("[1, 2, 3]"), ("4", "5", Bar(Foo("6", "7")) ))
130239
131240julia> 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.
133249
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))))
136253```
137254"""
138255function fmap (f, x; exclude = isleaf, walk = _default_walk, cache = IdDict ())
146263"""
147264 fmapstructure(f, x; exclude = isleaf)
148265
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.
150269
151270Useful for when the output must not contain custom structs.
152271
@@ -156,10 +275,19 @@ julia> struct Foo; x; y; end
156275
157276julia> @functor Foo
158277
159- julia> m = Foo([1,2,3], ( 4, 5) );
278+ julia> m = Foo([1,2,3], [ 4, (5, 6), Foo(7, 8)] );
160279
161280julia> 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)])
163291```
164292"""
165293fmapstructure (f, x; kwargs... ) = fmap (f, x; walk = (f, x) -> map (f, children (x)), kwargs... )
0 commit comments