From 2702ef247e3b5b3875e390816ccb35f7df63289b Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Mon, 6 Jan 2020 23:05:20 -0800 Subject: [PATCH 01/12] Allow non-Function callable as combine argument of merge[!] --- base/abstractdict.jl | 4 ++-- test/dict.jl | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/base/abstractdict.jl b/base/abstractdict.jl index 94fb94de2315e..dd310edfff18d 100644 --- a/base/abstractdict.jl +++ b/base/abstractdict.jl @@ -213,7 +213,7 @@ Dict{Int64,Int64} with 3 entries: 1 => 0 ``` """ -function merge!(combine::Function, d::AbstractDict, others::AbstractDict...) +function merge!(combine, d::AbstractDict, others::AbstractDict...) for other in others for (k,v) in other d[k] = haskey(d, k) ? combine(d[k], v) : v @@ -313,7 +313,7 @@ Dict{String,Float64} with 3 entries: "foo" => 0.0 ``` """ -merge(combine::Function, d::AbstractDict, others::AbstractDict...) = +merge(combine, d::AbstractDict, others::AbstractDict...) = merge!(combine, _typeddict(d, others...), others...) promoteK(K) = K diff --git a/test/dict.jl b/test/dict.jl index 0981f8ba7536f..cbd0507e681e9 100644 --- a/test/dict.jl +++ b/test/dict.jl @@ -917,6 +917,9 @@ let end end +struct NonFunctionCallable end +(::NonFunctionCallable)(args...) = +(args...) + @testset "Dict merge" begin d1 = Dict("A" => 1, "B" => 2) d2 = Dict("B" => 3.0, "C" => 4.0) @@ -925,6 +928,7 @@ end @test @inferred merge(+, d1, d2) == Dict("A" => 1, "B" => 5, "C" => 4) @test @inferred merge(*, d1, d2) == Dict("A" => 1, "B" => 6, "C" => 4) @test @inferred merge(-, d1, d2) == Dict("A" => 1, "B" => -1, "C" => 4) + @test @inferred merge(NonFunctionCallable(), d1, d2) == Dict("A" => 1, "B" => 5, "C" => 4) end @testset "Dict merge!" begin @@ -939,6 +943,8 @@ end @test d1 == Dict("A" => 1, "B" => 18, "C" => 32) @inferred merge!(-, d1, d2) @test d1 == Dict("A" => 1, "B" => 15, "C" => 28) + @inferred merge!(NonFunctionCallable(), d1, d2) + @test d1 == Dict("A" => 1, "B" => 21, "C" => 36) end @testset "Dict reduce merge" begin From e4ac61f849495c039399ec8c9cdac0b04a325864 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Tue, 7 Jan 2020 00:22:03 -0800 Subject: [PATCH 02/12] Disambiguate merge(::NamedTuple, ::AbstractDict) --- base/namedtuple.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/base/namedtuple.jl b/base/namedtuple.jl index 965466c71489e..43b51b144421b 100644 --- a/base/namedtuple.jl +++ b/base/namedtuple.jl @@ -265,6 +265,9 @@ function merge(a::NamedTuple, itr) merge(a, NamedTuple{(names...,)}((vals...,))) end +# Disambiguation: +merge(a::NamedTuple, itr::AbstractDict) = invoke(merge, Tuple{NamedTuple, Any}, a, itr) + keys(nt::NamedTuple{names}) where {names} = names values(nt::NamedTuple) = Tuple(nt) haskey(nt::NamedTuple, key::Union{Integer, Symbol}) = isdefined(nt, key) From eff4c891fb4b58663bdb2695645e2492000bc2ac Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Tue, 7 Jan 2020 02:21:01 -0800 Subject: [PATCH 03/12] Fix test --- test/dict.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/dict.jl b/test/dict.jl index cbd0507e681e9..96ebdd68f93fc 100644 --- a/test/dict.jl +++ b/test/dict.jl @@ -944,7 +944,7 @@ end @inferred merge!(-, d1, d2) @test d1 == Dict("A" => 1, "B" => 15, "C" => 28) @inferred merge!(NonFunctionCallable(), d1, d2) - @test d1 == Dict("A" => 1, "B" => 21, "C" => 36) + @test d1 == Dict("A" => 1, "B" => 18, "C" => 32) end @testset "Dict reduce merge" begin From 0d122aa32381e6141773be9ff02cc21e329629cb Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Wed, 8 Jan 2020 21:43:17 -0800 Subject: [PATCH 04/12] Revert "Disambiguate merge(::NamedTuple, ::AbstractDict)" This reverts commit e4ac61f849495c039399ec8c9cdac0b04a325864. --- base/namedtuple.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/base/namedtuple.jl b/base/namedtuple.jl index 43b51b144421b..965466c71489e 100644 --- a/base/namedtuple.jl +++ b/base/namedtuple.jl @@ -265,9 +265,6 @@ function merge(a::NamedTuple, itr) merge(a, NamedTuple{(names...,)}((vals...,))) end -# Disambiguation: -merge(a::NamedTuple, itr::AbstractDict) = invoke(merge, Tuple{NamedTuple, Any}, a, itr) - keys(nt::NamedTuple{names}) where {names} = names values(nt::NamedTuple) = Tuple(nt) haskey(nt::NamedTuple, key::Union{Integer, Symbol}) = isdefined(nt, key) From 6c02121cdd3459f97aec6876d61f4e0480816c06 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Wed, 8 Jan 2020 21:43:36 -0800 Subject: [PATCH 05/12] Revert "Allow non-Function callable as combine argument of merge[!]" This reverts commit 2702ef247e3b5b3875e390816ccb35f7df63289b. --- base/abstractdict.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/abstractdict.jl b/base/abstractdict.jl index dd310edfff18d..94fb94de2315e 100644 --- a/base/abstractdict.jl +++ b/base/abstractdict.jl @@ -213,7 +213,7 @@ Dict{Int64,Int64} with 3 entries: 1 => 0 ``` """ -function merge!(combine, d::AbstractDict, others::AbstractDict...) +function merge!(combine::Function, d::AbstractDict, others::AbstractDict...) for other in others for (k,v) in other d[k] = haskey(d, k) ? combine(d[k], v) : v @@ -313,7 +313,7 @@ Dict{String,Float64} with 3 entries: "foo" => 0.0 ``` """ -merge(combine, d::AbstractDict, others::AbstractDict...) = +merge(combine::Function, d::AbstractDict, others::AbstractDict...) = merge!(combine, _typeddict(d, others...), others...) promoteK(K) = K From 48997afb84e7d00b3608cc62133f50bc2ffc65f0 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Wed, 8 Jan 2020 21:58:45 -0800 Subject: [PATCH 06/12] Add mergewith[!] --- base/abstractdict.jl | 49 +++++++++++++++++++++++++++++++++++++------- base/exports.jl | 2 ++ test/dict.jl | 23 ++++++++++++++------- 3 files changed, 60 insertions(+), 14 deletions(-) diff --git a/base/abstractdict.jl b/base/abstractdict.jl index 94fb94de2315e..1889d4375e8f6 100644 --- a/base/abstractdict.jl +++ b/base/abstractdict.jl @@ -184,11 +184,21 @@ function merge!(d::AbstractDict, others::AbstractDict...) end """ - merge!(combine, d::AbstractDict, others::AbstractDict...) + mergewith!(combine, d::AbstractDict, others::AbstractDict...) -> d + mergewith!(combine) + merge!(combine, d::AbstractDict, others::AbstractDict...) -> d Update collection with pairs from the other collections. Values with the same key will be combined using the -combiner function. +combiner function. The curried form `mergewith!(combine)` returns the +function `(args...) -> mergewith!(combine, args...)`. + +Method `merge!(combine::Function, args...)` as an alias of +`mergewith!(combine, args...)` is still available for backward +compatibility. + +!!! compat "Julia 1.5" + `mergewith!` requires Julia 1.5 or later. # Examples ```jldoctest @@ -196,7 +206,7 @@ julia> d1 = Dict(1 => 2, 3 => 4); julia> d2 = Dict(1 => 4, 4 => 5); -julia> merge!(+, d1, d2); +julia> mergewith!(+, d1, d2); julia> d1 Dict{Int64,Int64} with 3 entries: @@ -204,16 +214,22 @@ Dict{Int64,Int64} with 3 entries: 3 => 4 1 => 6 -julia> merge!(-, d1, d1); +julia> mergewith!(-, d1, d1); julia> d1 Dict{Int64,Int64} with 3 entries: 4 => 0 3 => 0 1 => 0 + +julia> foldl(mergewith!(+), [d1, d2]; init=Dict{Int64,Int64}()) +Dict{Int64,Int64} with 3 entries: + 4 => 5 + 3 => 0 + 1 => 4 ``` """ -function merge!(combine::Function, d::AbstractDict, others::AbstractDict...) +function mergewith!(combine, d::AbstractDict, others::AbstractDict...) for other in others for (k,v) in other d[k] = haskey(d, k) ? combine(d[k], v) : v @@ -222,6 +238,10 @@ function merge!(combine::Function, d::AbstractDict, others::AbstractDict...) return d end +mergewith!(combine) = (args...) -> mergewith!(combine, args...) + +merge!(combine::Function, args...) = mergewith!(combine, args...) + """ keytype(type) @@ -287,12 +307,21 @@ merge(d::AbstractDict, others::AbstractDict...) = merge!(_typeddict(d, others...), others...) """ + mergewith(combine, d::AbstractDict, others::AbstractDict...) + mergewith(combine) merge(combine, d::AbstractDict, others::AbstractDict...) Construct a merged collection from the given collections. If necessary, the types of the resulting collection will be promoted to accommodate the types of the merged collections. Values with the same key will be combined using the -combiner function. +combiner function. The curried form `mergewith(combine)` returns the function +`(args...) -> mergewith(combine, args...)`. + +Method `merge(combine::Function, args...)` as an alias of +`mergewith(combine, args...)` is still available for backward compatibility. + +!!! compat "Julia 1.5" + `mergewith` requires Julia 1.5 or later. # Examples ```jldoctest @@ -306,13 +335,19 @@ Dict{String,Int64} with 2 entries: "bar" => 4711 "baz" => 17 -julia> merge(+, a, b) +julia> mergewith(+, a, b) Dict{String,Float64} with 3 entries: "bar" => 4753.0 "baz" => 17.0 "foo" => 0.0 + +julia> ans == mergewith(+)(a, b) +true ``` """ +mergewith(combine, d::AbstractDict, others::AbstractDict...) = + mergewith!(combine, _typeddict(d, others...), others...) +mergewith(combine) = (args...) -> mergewith(combine, args...) merge(combine::Function, d::AbstractDict, others::AbstractDict...) = merge!(combine, _typeddict(d, others...), others...) diff --git a/base/exports.jl b/base/exports.jl index 90e177fc8a9f7..eb1980739df9a 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -514,7 +514,9 @@ export mapfoldr, mapreduce, merge!, + mergewith!, merge, + mergewith, pairs, reduce, setdiff!, diff --git a/test/dict.jl b/test/dict.jl index 96ebdd68f93fc..40b567d9fe499 100644 --- a/test/dict.jl +++ b/test/dict.jl @@ -925,10 +925,14 @@ struct NonFunctionCallable end d2 = Dict("B" => 3.0, "C" => 4.0) @test @inferred merge(d1, d2) == Dict("A" => 1, "B" => 3, "C" => 4) # merge with combiner function + @test @inferred mergewith(+, d1, d2) == Dict("A" => 1, "B" => 5, "C" => 4) + @test @inferred mergewith(*, d1, d2) == Dict("A" => 1, "B" => 6, "C" => 4) + @test @inferred mergewith(-, d1, d2) == Dict("A" => 1, "B" => -1, "C" => 4) + @test @inferred mergewith(NonFunctionCallable(), d1, d2) == Dict("A" => 1, "B" => 5, "C" => 4) + @test foldl(mergewith(+), [d1, d2]; init=Dict{Union{},Union{}}()) == + Dict("A" => 1, "B" => 5, "C" => 4) + # backward compatibility @test @inferred merge(+, d1, d2) == Dict("A" => 1, "B" => 5, "C" => 4) - @test @inferred merge(*, d1, d2) == Dict("A" => 1, "B" => 6, "C" => 4) - @test @inferred merge(-, d1, d2) == Dict("A" => 1, "B" => -1, "C" => 4) - @test @inferred merge(NonFunctionCallable(), d1, d2) == Dict("A" => 1, "B" => 5, "C" => 4) end @testset "Dict merge!" begin @@ -937,14 +941,19 @@ end @inferred merge!(d1, d2) @test d1 == Dict("A" => 1, "B" => 3, "C" => 4) # merge! with combiner function - @inferred merge!(+, d1, d2) + @inferred mergewith!(+, d1, d2) @test d1 == Dict("A" => 1, "B" => 6, "C" => 8) - @inferred merge!(*, d1, d2) + @inferred mergewith!(*, d1, d2) @test d1 == Dict("A" => 1, "B" => 18, "C" => 32) - @inferred merge!(-, d1, d2) + @inferred mergewith!(-, d1, d2) @test d1 == Dict("A" => 1, "B" => 15, "C" => 28) - @inferred merge!(NonFunctionCallable(), d1, d2) + @inferred mergewith!(NonFunctionCallable(), d1, d2) @test d1 == Dict("A" => 1, "B" => 18, "C" => 32) + @test foldl(mergewith!(+), [d1, d2]; init=Dict(d1)) == + Dict("A" => 1, "B" => 21, "C" => 36) + # backward compatibility + merge!(+, d1, d2) + @test d1 == Dict("A" => 1, "B" => 21, "C" => 36) end @testset "Dict reduce merge" begin From dd947298db3fca11c8aa82e59ed6dad08be5c8c6 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Thu, 9 Jan 2020 00:07:26 -0800 Subject: [PATCH 07/12] Fix at-docs; include mergewith[!] in documentation --- doc/src/base/collections.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/src/base/collections.md b/doc/src/base/collections.md index d947786689056..0624a9233864a 100644 --- a/doc/src/base/collections.md +++ b/doc/src/base/collections.md @@ -210,8 +210,9 @@ Base.keys Base.values Base.pairs Base.merge -Base.merge!(::AbstractDict, ::AbstractDict...) -Base.merge!(::Function, ::AbstractDict, ::AbstractDict...) +Base.mergewith +Base.merge! +Base.mergewith! Base.sizehint! Base.keytype Base.valtype From bc34a534bfa11005ba3b22974c245d69f5d49bbd Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Thu, 9 Jan 2020 00:57:19 -0800 Subject: [PATCH 08/12] Fix test --- test/dict.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/dict.jl b/test/dict.jl index 40b567d9fe499..830d14c961462 100644 --- a/test/dict.jl +++ b/test/dict.jl @@ -949,7 +949,7 @@ end @test d1 == Dict("A" => 1, "B" => 15, "C" => 28) @inferred mergewith!(NonFunctionCallable(), d1, d2) @test d1 == Dict("A" => 1, "B" => 18, "C" => 32) - @test foldl(mergewith!(+), [d1, d2]; init=Dict(d1)) == + @test foldl(mergewith!(+), [d1, d2]; init=empty(d1)) == Dict("A" => 1, "B" => 21, "C" => 36) # backward compatibility merge!(+, d1, d2) From 4510e7a914ea9f65f86bfdf2acddc9b905e6e8e3 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Thu, 9 Jan 2020 12:51:11 -0800 Subject: [PATCH 09/12] Add NEWS --- NEWS.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS.md b/NEWS.md index 139c9238de7f1..52266dd923740 100644 --- a/NEWS.md +++ b/NEWS.md @@ -20,6 +20,10 @@ Build system changes New library functions --------------------- +* New functions `mergewith` and `mergewith!` supersede `merge` and `merge!` with `combine` + argument. They don't have the restriction for `combine` to be a `Function` and also + provide one-argument method that returns a closure. The old methods of `merge` and + `merge!` are still available for backward compatibility ([#34296]). New library features -------------------- From 13877edda85a4eb85f77d2a8790fa992f07c1070 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Thu, 16 Jan 2020 13:47:44 -0800 Subject: [PATCH 10/12] Update base/abstractdict.jl Co-Authored-By: Jameson Nash --- base/abstractdict.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/abstractdict.jl b/base/abstractdict.jl index 1889d4375e8f6..4d85d2027cfc0 100644 --- a/base/abstractdict.jl +++ b/base/abstractdict.jl @@ -240,7 +240,7 @@ end mergewith!(combine) = (args...) -> mergewith!(combine, args...) -merge!(combine::Function, args...) = mergewith!(combine, args...) +merge!(combine::Callable, args...) = mergewith!(combine, args...) """ keytype(type) From cad1e65edfa7ea14b79c2a5e8b952601802bcc04 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Thu, 16 Jan 2020 13:59:39 -0800 Subject: [PATCH 11/12] Update base/abstractdict.jl Co-Authored-By: Jeff Bezanson --- base/abstractdict.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/abstractdict.jl b/base/abstractdict.jl index 4d85d2027cfc0..30fea0560a2ad 100644 --- a/base/abstractdict.jl +++ b/base/abstractdict.jl @@ -348,7 +348,7 @@ true mergewith(combine, d::AbstractDict, others::AbstractDict...) = mergewith!(combine, _typeddict(d, others...), others...) mergewith(combine) = (args...) -> mergewith(combine, args...) -merge(combine::Function, d::AbstractDict, others::AbstractDict...) = +merge(combine::Callable, d::AbstractDict, others::AbstractDict...) = merge!(combine, _typeddict(d, others...), others...) promoteK(K) = K From 4fc493a286fdb132809cf8fc6b4789db10afea39 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Thu, 16 Jan 2020 14:01:18 -0800 Subject: [PATCH 12/12] Mention that merge[!] is still callable with Union{Function,Type} --- base/abstractdict.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/abstractdict.jl b/base/abstractdict.jl index 30fea0560a2ad..92d2ff06aeec8 100644 --- a/base/abstractdict.jl +++ b/base/abstractdict.jl @@ -193,7 +193,7 @@ Values with the same key will be combined using the combiner function. The curried form `mergewith!(combine)` returns the function `(args...) -> mergewith!(combine, args...)`. -Method `merge!(combine::Function, args...)` as an alias of +Method `merge!(combine::Union{Function,Type}, args...)` as an alias of `mergewith!(combine, args...)` is still available for backward compatibility. @@ -317,7 +317,7 @@ the merged collections. Values with the same key will be combined using the combiner function. The curried form `mergewith(combine)` returns the function `(args...) -> mergewith(combine, args...)`. -Method `merge(combine::Function, args...)` as an alias of +Method `merge(combine::Union{Function,Type}, args...)` as an alias of `mergewith(combine, args...)` is still available for backward compatibility. !!! compat "Julia 1.5"