API
Functors.AbstractWalk
Functors.CachedWalk
Functors.CollectWalk
Functors.DefaultWalk
Functors.ExcludeWalk
Functors.IterateWalk
Functors.KeyPath
Functors.StructuralWalk
Functors.children
Functors.execute
Functors.fcollect
Functors.fleaves
Functors.fmap
Functors.fmap_with_path
Functors.fmapstructure
Functors.fmapstructure_with_path
Functors.functor
Functors.getkeypath
Functors.haskeypath
Functors.isleaf
Functors.setkeypath!
Functors.@functor
Functors.@leaf
Constructors and helpers
Functors.@functor
— Macro@functor T
@functor T (x,)
Adds methods to functor
allowing recursion into objects of type T
, and reconstruction. Assumes that T
has a constructor accepting all of its fields, which is true unless you have provided an inner constructor which does not.
By default all fields of T
are considered children
; this can be restricted be restructed by providing a tuple of field names.
Examples
julia> struct Foo; x; y; end
julia> Functors.children(Foo(1,2))
(x = 1, y = 2)
julia> _, re = Functors.functor(Foo(1,2));
julia> re((10, 20))
Foo(10, 20)
julia> @functor Foo # same as before, nothing changes
julia> struct TwoThirds a; b; c; end
julia> @functor TwoThirds (a, c)
julia> ch2, re3 = Functors.functor(TwoThirds(10,20,30));
julia> ch2
(a = 10, c = 30)
julia> re3(("ten", "thirty"))
TwoThirds("ten", 20, "thirty")
julia> fmap(x -> 10x, TwoThirds(Foo(1,2), Foo(3,4), 56))
TwoThirds(Foo(10, 20), Foo(3, 4), 560)
Functors.@leaf
— Macro@leaf T
Define functor
for the type T
so that isleaf(x::T) == true
.
Functors.functor
— Functionfunctor(x)
functor(typeof(x), x)
Returns a tuple containing, first, a NamedTuple
of the children of x
(typically its fields), and second, a reconstruction function. This controls the behaviour of fmap
.
Methods should be added to functor(::Type{T}, x)
for custom types, usually using the macro @functor
.
Functors.children
— Functionchildren(x)
Return the children of x
as defined by functor
. Equivalent to functor(x)[1]
.
Functors.isleaf
— Functionisleaf(x)
Return true if x
has no children
according to functor
.
Examples
julia> Functors.isleaf(1)
true
julia> Functors.isleaf([2, 3, 4])
true
julia> Functors.isleaf(["five", [6, 7]])
false
julia> Functors.isleaf([])
false
julia> Functors.isleaf((8, 9))
false
julia> Functors.isleaf(())
true
Functors.fcollect
— Functionfcollect(x; exclude = v -> false)
Traverse x
by recursing each child of x
as defined by functor
and collecting the results into a flat array, ordered by a breadth-first traversal of x
, respecting the iteration order of children
calls.
Doesn't recurse inside branches rooted at nodes v
for which exclude(v) == true
. In such cases, the root v
is also excluded from the result. By default, exclude
always yields false
.
See also children
.
Examples
julia> struct Foo; x; y; end
julia> struct Bar; x; end
julia> struct TypeWithNoChildren; x; y; end
julia> @leaf TypeWithNoChildren
julia> m = Foo(Bar([1,2,3]), TypeWithNoChildren(:a, :b))
Foo(Bar([1, 2, 3]), TypeWithNoChildren(:a, :b))
julia> fcollect(m)
4-element Vector{Any}:
Foo(Bar([1, 2, 3]), TypeWithNoChildren(:a, :b))
Bar([1, 2, 3])
[1, 2, 3]
TypeWithNoChildren(:a, :b)
julia> fcollect(m, exclude = v -> v isa Bar)
2-element Vector{Any}:
Foo(Bar([1, 2, 3]), TypeWithNoChildren(:a, :b))
TypeWithNoChildren(:a, :b)
julia> fcollect(m, exclude = v -> Functors.isleaf(v))
2-element Vector{Any}:
Foo(Bar([1, 2, 3]), TypeWithNoChildren(:a, :b))
Bar([1, 2, 3])
Functors.fleaves
— Functionfleaves(x; exclude = isleaf)
Traverse x
by recursing each child of x
as defined by functor
and collecting the leaves into a flat array, ordered by a breadth-first traversal of x
, respecting the iteration order of children
calls.
The exclude
function is used to determine whether to recurse into a node, therefore identifying the leaves as the nodes for which exclude
returns true
.
See also fcollect
for a similar function that collects all nodes instead.
Examples
julia> struct Bar; x; end
julia> struct TypeWithNoChildren; x; y; end
julia> @leaf TypeWithNoChildren
julia> m = (a = Bar([1,2,3]), b = TypeWithNoChildren(4, 5));
julia> fleaves(m)
2-element Vector{Any}:
[1, 2, 3]
TypeWithNoChildren(4, 5)
Maps
Functors.fmap
— Functionfmap(f, x, ys...; exclude = Functors.isleaf, walk = Functors.DefaultWalk(), [prune])
A structure and type preserving map
.
By default it transforms every leaf node (identified by exclude
, default isleaf
) by applying f
, and otherwise traverses x
recursively using functor
. Optionally, it may also be associated with objects ys
with the same tree structure. In that case, f
is applied to the corresponding leaf nodes in x
and ys
.
See also fmap_with_path
and fmapstructure
.
Examples
julia> fmap(string, (x=1, y=(2, 3)))
(x = "1", y = ("2", "3"))
julia> nt = (a = [1,2], b = [23, (45,), (x=6//7, y=())], c = [8,9]);
julia> fmap(println, nt)
[1, 2]
23
45
6//7
()
[8, 9]
(a = nothing, b = Any[nothing, (nothing,), (x = nothing, y = nothing)], c = nothing)
julia> fmap(println, nt; exclude = x -> x isa Array)
[1, 2]
Any[23, (45,), (x = 6//7, y = ())]
[8, 9]
(a = nothing, b = nothing, c = nothing)
julia> twice = [1, 2]; # println only acts once on this
julia> fmap(println, (i = twice, ii = 34, iii = [5, 6], iv = (twice, 34), v = 34.0))
[1, 2]
34
[5, 6]
34
34.0
(i = nothing, ii = nothing, iii = nothing, iv = (nothing, nothing), v = nothing)
julia> d1 = Dict("x" => [1,2], "y" => 3);
julia> d2 = Dict("x" => [4,5], "y" => 6, "z" => "an_extra_value");
julia> fmap(+, d1, d2) == Dict("x" => [5, 7], "y" => 9) # Note that "z" is ignored
true
Mutable objects which appear more than once are only handled once (by caching f(x)
in an IdDict
). Thus the relationship x.i === x.iv[1]
will be preserved. An immutable object which appears twice is not stored in the cache, thus f(34)
will be called twice, and the results will agree only if f
is pure.
By default, almost all container-like types have children to recurse into. Arrays of numbers do not.
To opt out of recursion for custom types use @leaf
or pass a custom exclude
function.
julia> struct Foo; x; y; end
julia> struct Bar; x; end
julia> m = Foo(Bar([1,2,3]), (4, 5, Bar(Foo(6, 7))));
julia> fmap(x -> 10x, m)
Foo(Bar([10, 20, 30]), (40, 50, Bar(Foo(60, 70))))
julia> fmap(string, m)
Foo(Bar("[1, 2, 3]"), ("4", "5", Bar(Foo("6", "7"))))
julia> fmap(string, m, exclude = v -> v isa Bar)
Foo("Bar([1, 2, 3])", (4, 5, "Bar(Foo(6, 7))"))
To recurse into custom types without reconstructing them afterwards, use fmapstructure
.
For advanced customization of the traversal behaviour, pass a custom walk
function that subtypes Functors.AbstractWalk
. The call fmap(f, x, ys...; walk = mywalk)
will wrap mywalk
in ExcludeWalk
then CachedWalk
. Here, ExcludeWalk
is responsible for applying f
at excluded nodes. For a low-level interface for executing a user-constructed walk, see execute
.
julia> struct MyWalk <: Functors.AbstractWalk end
julia> (::MyWalk)(recurse, x) = x isa Bar ? "hello" :
Functors.DefaultWalk()(recurse, x)
julia> fmap(x -> 10x, m; walk = MyWalk())
Foo("hello", (40, 50, "hello"))
The behaviour when the same node appears twice can be altered by giving a value to the prune
keyword, which is then used in place of all but the first:
julia> twice = [1, 2];
julia> fmap(float, (x = twice, y = [1,2], z = twice); prune = missing)
(x = [1.0, 2.0], y = [1.0, 2.0], z = missing)
Functors.fmap_with_path
— Functionfmap_with_path(f, x, ys...; exclude = isleaf, walk = DefaultWalkWithPath(), [prune])
Like fmap
, but also passes a KeyPath
to f
for each node in the recursion. The KeyPath
is a tuple of the indices used to reach the current node from the root of the recursion. The KeyPath
is constructed by the walk
function, and can be used to reconstruct the path to the current node from the root of the recursion.
f
has to accept two arguments: the associated KeyPath
and the value of the current node.
exclude
also receives the KeyPath
as its first argument and a node as its second. It should return true
if the recursion should not continue on its children and f
applied to it.
prune
is used to control the behaviour when the same node appears twice, see fmap
for more information.
Examples
julia> x = ([1, 2, 3], 4, (a=5, b=Dict("A"=>6, "B"=>7), c=Dict("C"=>8, "D"=>9)));
julia> exclude(kp, x) = kp == KeyPath(3, :c) || Functors.isleaf(x);
julia> fmap_with_path((kp, x) -> x isa Dict ? nothing : x.^2, x; exclude = exclude)
([1, 4, 9], 16, (a = 25, b = Dict("B" => 49, "A" => 36), c = nothing))
Functors.fmapstructure
— Functionfmapstructure(f, x, ys...; exclude = isleaf, [prune])
Like fmap
, but doesn't preserve the type of custom structs. Instead, it returns a NamedTuple
(or a Tuple
, or an array), or a nested set of these.
Useful for when the output must not contain custom structs.
See also fmap
and fmapstructure_with_path
.
Examples
julia> struct Foo; x; y; end
julia> m = Foo([1,2,3], [4, (5, 6), Foo(7, 8)]);
julia> fmapstructure(x -> 2x, m)
(x = [2, 4, 6], y = Any[8, (10, 12), (x = 14, y = 16)])
julia> fmapstructure(println, m)
[1, 2, 3]
4
5
6
7
8
(x = nothing, y = Any[nothing, (nothing, nothing), (x = nothing, y = nothing)])
Functors.fmapstructure_with_path
— Functionfmapstructure_with_path(f, x, ys...; [exclude, prune])
Like fmap_with_path
, but doesn't preserve the type of custom structs. Instead, it returns a named tuple, a tuple, an array, a dict, or a nested set of these.
See also fmapstructure
.
Walks
Functors.AbstractWalk
— TypeAbstractWalk
Any walk for use with fmap
should inherit from this type. A walk subtyping AbstractWalk
must satisfy the walk function interface:
struct MyWalk <: AbstractWalk end
function (::MyWalk)(recurse, x, ys...)
# implement this
end
The walk function is called on a node x
in a Functors tree. It may also be passed associated nodes ys...
in other Functors trees. The walk function recurses further into (x, ys...)
by calling recurse
on the child nodes. The choice of which nodes to recurse and in what order is custom to the walk.
Functors.execute
— Functionexecute(walk, x, ys...)
Execute a walk
that recursively calls itself, starting at a node x
in a Functors tree, as well as optional associated nodes ys...
in other Functors trees. Any custom walk
function that subtypes Functors.AbstractWalk
is permitted.
Functors.DefaultWalk
— TypeDefaultWalk()
The default walk behavior for Functors.jl. Walks all the Functors.children
of trees (x, ys...)
based on the structure of x
. The resulting mapped child nodes are restructured into the type of x
.
See fmap
for more information.
Functors.StructuralWalk
— TypeStructuralWalk()
A structural variant of Functors.DefaultWalk
. The recursion behavior is identical, but the mapped children are not restructured.
See fmapstructure
for more information.
Functors.ExcludeWalk
— TypeExcludeWalk(walk, fn, exclude)
A walk that recurses nodes (x, ys...)
according to walk
, except when exclude(x)
is true. Then, fn(x, ys...)
is applied instead of recursing further.
Typically wraps an existing walk
for use with fmap
.
Functors.CachedWalk
— TypeCachedWalk(walk[; prune])
A walk that recurses nodes (x, ys...)
according to walk
and storing the output of the recursion in a cache indexed by x
(based on object ID). Whenever the cache already contains x
, either:
prune
is specified, then it is returned, orprune
is unspecified, and the previously cached recursion of(x, ys...)
returned.
Typically wraps an existing walk
for use with fmap
.
Functors.CollectWalk
— TypeCollectWalk()
A walk that recurses into a node x
via Functors.children
, storing the recursion history in a cache. The resulting ordered recursion history is returned.
See fcollect
for more information.
Functors.IterateWalk
— TypeIterateWalk()
A walk that walks all the Functors.children
of trees (x, ys...)
and concatenates the iterators of the children via Iterators.flatten
. The resulting iterator is returned.
When used with fmap
, the provided function f
should return an iterator. For example, to iterate through the square of every scalar value:
julia> x = ([1, 2, 3], 4, (5, 6, [7, 8]));
julia> make_iterator(x) = x isa AbstractVector ? x.^2 : (x^2,);
julia> iter = fmap(make_iterator, x; walk=Functors.IterateWalk(), cache=nothing);
julia> collect(iter)
8-element Vector{Int64}:
1
4
9
16
25
36
49
64
We can also simultaneously iterate through multiple functors:
julia> y = ([8, 7, 6], 5, (4, 3, [2, 1]));
julia> make_zipped_iterator(x, y) = zip(make_iterator(x), make_iterator(y));
julia> zipped_iter = fmap(make_zipped_iterator, x, y; walk=Functors.IterateWalk(), cache=nothing);
julia> collect(zipped_iter)
8-element Vector{Tuple{Int64, Int64}}:
(1, 64)
(4, 49)
(9, 36)
(16, 25)
(25, 16)
(36, 9)
(49, 4)
(64, 1)
KeyPath
Functors.KeyPath
— TypeKeyPath(keys...)
A type for representing a path of keys to a value in a nested structure. Can be constructed with a sequence of keys, or by concatenating other KeyPath
s. Keys can be of type Symbol
, String
, or Int
.
For custom types, access through symbol keys is assumed to be done with getproperty
. For consistency, the method Base.propertynames
is used to get the viable property names.
For string and integer keys instead, the access is done with getindex
.
See also getkeypath
, haskeypath
.
Examples
julia> kp = KeyPath(:b, 3)
KeyPath(:b, 3)
julia> KeyPath(:a, kp, :c, 4) # construct mixing keys and keypaths
KeyPath(:a, :b, 3, :c, 4)
julia> struct T
a
b
end
julia> function Base.getproperty(x::T, k::Symbol)
if k in fieldnames(T)
return getfield(x, k)
elseif k === :ab
return "ab"
else
error()
end
end;
julia> Base.propertynames(::T) = (:a, :b, :ab);
julia> x = T(3, Dict(:c => 4, :d => 5));
julia> getkeypath(x, KeyPath(:ab)) # equivalent to x.ab
"ab"
julia> getkeypath(x, KeyPath(:b, :c)) # equivalent to (x.b)[:c]
4
Functors.haskeypath
— Functionhaskeypath(x, kp::KeyPath)
Return true
if x
has a value at the path kp
.
See also KeyPath
, getkeypath
, and setkeypath!
.
Examples
julia> x = Dict(:a => 3, :b => Dict(:c => 4, "d" => [5, 6, 7]))
Dict{Symbol, Any} with 2 entries:
:a => 3
:b => Dict{Any, Any}(:c=>4, "d"=>[5, 6, 7])
julia> haskeypath(x, KeyPath(:a))
true
julia> haskeypath(x, KeyPath(:b, "d", 1))
true
julia> haskeypath(x, KeyPath(:b, "d", 4))
false
Functors.getkeypath
— Functiongetkeypath(x, kp::KeyPath)
Return the value in x
at the path kp
.
See also KeyPath
, haskeypath
, and setkeypath!
.
Examples
julia> x = Dict(:a => 3, :b => Dict(:c => 4, "d" => [5, 6, 7]))
Dict{Symbol, Any} with 2 entries:
:a => 3
:b => Dict{Any, Any}(:c=>4, "d"=>[5, 6, 7])
julia> getkeypath(x, KeyPath(:b, "d", 2))
6
Functors.setkeypath!
— Functionsetkeypath!(x, kp::KeyPath, v)
Set the value in x
at the path kp
to v
.
See also KeyPath
, getkeypath
, and haskeypath
.