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