Skip to content

Commit 6778507

Browse files
add function fleaves (#73)
* define fflatten * improve docstring * add to docs * cl/fflatten
1 parent 942126f commit 6778507

File tree

5 files changed

+63
-4
lines changed

5 files changed

+63
-4
lines changed

docs/src/api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,5 @@ Functors.IterateWalk
2525
```@docs
2626
Functors.fmapstructure
2727
Functors.fcollect
28+
Functors.fleaves
2829
```

src/Functors.jl

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module Functors
22

3-
export @functor, @flexiblefunctor, fmap, fmapstructure, fcollect, execute
3+
export @functor, @flexiblefunctor, fmap, fmapstructure, fcollect, execute, fleaves
44

55
include("functor.jl")
66
include("walks.jl")
@@ -303,4 +303,36 @@ julia> fcollect(m, exclude = v -> Functors.isleaf(v))
303303
"""
304304
fcollect
305305

306+
307+
308+
"""
309+
fleaves(x; exclude = isleaf)
310+
311+
Traverse `x` by recursing each child of `x` as defined by [`functor`](@ref)
312+
and collecting the leaves into a flat array,
313+
ordered by a breadth-first traversal of `x`, respecting the iteration order of `children` calls.
314+
315+
The `exclude` function is used to determine whether to recurse into a node, therefore
316+
identifying the leaves as the nodes for which `exclude` returns `true`.
317+
318+
See also [`fcollect`](@ref) for a similar function that collects all nodes instead.
319+
320+
# Examples
321+
322+
```jldoctest
323+
julia> struct Bar; x; end
324+
325+
julia> @functor Bar
326+
327+
julia> struct TypeWithNoChildren; x; y; end
328+
329+
julia> m = (a=Bar([1,2,3]), b=TypeWithNoChildren(4, 5))
330+
331+
julia> fleaves(m)
332+
2-element Vector{Any}:
333+
[1, 2, 3]
334+
TypeWithNoChildren(:a, :b)
335+
"""
336+
fleaves
337+
306338
end # module

src/maps.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@ fmapstructure(f, x; kwargs...) = fmap(f, x; walk = StructuralWalk(), kwargs...)
1515

1616
fcollect(x; exclude = v -> false) =
1717
execute(ExcludeWalk(CollectWalk(), _ -> nothing, exclude), x)
18+
19+
fleaves(x; exclude = isleaf) =
20+
execute(ExcludeWalk(FlattenWalk(), x -> [x], exclude), x)

src/walks.jl

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,17 @@ julia> collect(zipped_iter)
234234
struct IterateWalk <: AbstractWalk end
235235

236236
function (walk::IterateWalk)(recurse, x, ys...)
237-
func, _ = functor(x)
238-
yfuncs = map(y -> functor(typeof(x), y)[1], ys)
239-
return Iterators.flatten(_map(recurse, func, yfuncs...))
237+
x_children = children(x)
238+
ys_children = map(children, ys)
239+
return Iterators.flatten(_map(recurse, x_children, ys_children...))
240240
end
241+
242+
struct FlattenWalk <: AbstractWalk end
243+
244+
function (walk::FlattenWalk)(recurse, x, ys...)
245+
x_children = _values(children(x))
246+
ys_children = map(children, ys)
247+
res = _map(recurse, x_children, ys_children...)
248+
return reduce(vcat, _values(res))
249+
end
250+

test/basics.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,3 +392,16 @@ end
392392
@testset "Deprecated first-arg walk API to fmap" begin
393393
@test (@test_deprecated fmap(Functors.DefaultWalk(), nothing, (1, 2, 3))) == (1, 2, 3)
394394
end
395+
396+
@testset "fleaves" begin
397+
x = (1, (2, 3), (a=4, b=(5, 6), c=7));
398+
@test fleaves(x) == [1, 2, 3, 4, 5, 6, 7]
399+
@test fleaves(x, exclude=x -> Functors.isleaf(x) || (x isa NamedTuple)) == [1, 2, 3, (a = 4, b = (5, 6), c = 7)]
400+
401+
x = Dict("a" => Foo(1, 2), "b" => Bar([1,2,3]))
402+
xflat = fleaves(x)
403+
# @test xflat== [1, 2, [1, 2, 3]] # cannot guarantee ordering with Dict
404+
@test xflat isa Vector
405+
@test length(xflat) == 3
406+
@test 1 xflat && 2 xflat && [1, 2, 3] xflat
407+
end

0 commit comments

Comments
 (0)