Skip to content

Commit 5ce988b

Browse files
functorize Dict (#53)
* functor Dict * cleanup * support multiple arguments in fmap * cleanup * ignore extra keys example * cleanup
1 parent 65e0c71 commit 5ce988b

File tree

4 files changed

+46
-4
lines changed

4 files changed

+46
-4
lines changed

src/Functors.jl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ A structure and type preserving `map`.
111111
112112
By default it transforms every leaf node (identified by `exclude`, default [`isleaf`](@ref))
113113
by applying `f`, and otherwise traverses `x` recursively using [`functor`](@ref).
114+
Optionally, it may also be associated with objects `ys` with the same tree structure.
115+
In that case, `f` is applied to the corresponding leaf nodes in `x` and `ys`.
114116
115117
# Examples
116118
```jldoctest
@@ -143,6 +145,13 @@ julia> fmap(println, (i = twice, ii = 34, iii = [5, 6], iv = (twice, 34), v = 34
143145
34
144146
34.0
145147
(i = nothing, ii = nothing, iii = nothing, iv = (nothing, nothing), v = nothing)
148+
149+
julia> d1 = Dict("x" => [1,2], "y" => 3);
150+
151+
julia> d2 = Dict("x" => [4,5], "y" => 6, "z" => "an_extra_value");
152+
153+
julia> fmap(+, d1, d2) == Dict("x" => [5, 7], "y" => 9) # Note that "z" is ignored
154+
true
146155
```
147156
148157
Mutable objects which appear more than once are only handled once (by caching `f(x)` in an `IdDict`).

src/functor.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ functor(x) = functor(typeof(x), x)
44

55
functor(::Type{<:Tuple}, x) = x, identity
66
functor(::Type{<:NamedTuple{L}}, x) where L = NamedTuple{L}(map(s -> getproperty(x, s), L)), identity
7+
functor(::Type{<:Dict}, x) = Dict(k => x[k] for k in keys(x)), identity
78

89
functor(::Type{<:AbstractArray}, x) = x, identity
910
functor(::Type{<:AbstractArray{<:Number}}, x) = (), _ -> x

src/walks.jl

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
_map(f, x...) = map(f, x...)
2+
_map(f, x::Dict, ys...) = Dict(k => f(v, (y[k] for y in ys)...) for (k, v) in x)
3+
4+
_values(x) = x
5+
_values(x::Dict) = values(x)
6+
17
"""
28
AbstractWalk
39
@@ -22,7 +28,7 @@ abstract type AbstractWalk end
2228
AnonymousWalk(walk_fn)
2329
2430
Wrap a `walk_fn` so that `AnonymousWalk(walk_fn) isa AbstractWalk`.
25-
This type only exists for backwards compatability and should be directly used.
31+
This type only exists for backwards compatability and should not be directly used.
2632
Attempting to wrap an existing `AbstractWalk` is a no-op (i.e. it is not wrapped).
2733
"""
2834
struct AnonymousWalk{F} <: AbstractWalk
@@ -53,7 +59,7 @@ struct DefaultWalk <: AbstractWalk end
5359
function (::DefaultWalk)(recurse, x, ys...)
5460
func, re = functor(x)
5561
yfuncs = map(y -> functor(typeof(x), y)[1], ys)
56-
re(map(recurse, func, yfuncs...))
62+
re(_map(recurse, func, yfuncs...))
5763
end
5864

5965
"""
@@ -66,7 +72,7 @@ See [`fmapstructure`](@ref) for more information.
6672
"""
6773
struct StructuralWalk <: AbstractWalk end
6874

69-
(::StructuralWalk)(recurse, x) = map(recurse, children(x))
75+
(::StructuralWalk)(recurse, x) = _map(recurse, children(x))
7076

7177
"""
7278
ExcludeWalk(walk, fn, exclude)
@@ -156,7 +162,7 @@ function (walk::CollectWalk)(recurse, x)
156162
# to exclude, we wrap this walk in ExcludeWalk
157163
usecache(walk.cache, x) && push!(walk.cache, x)
158164
push!(walk.output, x)
159-
map(recurse, children(x))
165+
_map(recurse, children(x))
160166

161167
return walk.output
162168
end

test/basics.jl

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,3 +326,29 @@ end
326326
m4 = FBar(m3, (:x,))
327327
@test all(fcollect(m4) .=== [m4, m3, m2, m0])
328328
end
329+
330+
@testset "Dict" begin
331+
d = Dict(:a => 1, :b => 2)
332+
333+
@test Functors.children(d) == d
334+
@test fmap(x -> x + 1, d) == Dict(:a => 2, :b => 3)
335+
336+
d = Dict(:a => 1, :b => Dict("a" => 5, "b" => 6, "c" => 7))
337+
@test Functors.children(d) == d
338+
@test fmap(x -> x + 1, d) == Dict(:a => 2, :b => Dict("a" => 6, "b" => 7, "c" => 8))
339+
340+
@testset "fmap(+, x, y)" begin
341+
m1 = Dict("x" => [1,2], "y" => 3)
342+
n1 = Dict("x" => [4,5], "y" => 6)
343+
@test fmap(+, m1, n1) == Dict("x" => [5, 7], "y" => 9)
344+
345+
m1 = Dict(:x => [1,2], :y => 3)
346+
n1 = (x = [4,5], y = 6)
347+
@test fmap(+, m1, n1) == Dict(:x => [5, 7], :y => 9)
348+
349+
# extra keys in n1 are ignored
350+
m1 = Dict("x" => [1,2], "y" => Dict(:a => 3, :b => 4))
351+
n1 = Dict("x" => [4,5], "y" => Dict(:a => 0.1, :b => 0.2, :c => 5), "z" => Dict(:a => 5))
352+
@test fmap(+, m1, n1) == Dict("x" => [5, 7], "y" => Dict(:a=>3.1, :b=>4.2))
353+
end
354+
end

0 commit comments

Comments
 (0)