@@ -5,4 +5,272 @@ export @functor, @flexiblefunctor, fmap, fmapstructure, fcollect
55include (" functor.jl" )
66include (" base.jl" )
77
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+
8276end # module
0 commit comments