fslang-suggestions icon indicating copy to clipboard operation
fslang-suggestions copied to clipboard

Add Map.merge

Open kjnilsson opened this issue 8 years ago • 9 comments

I propose we add a Map.merge m1 m2 function to merge two maps. Whenever there is a key conflict the value in m1 is superseded by the value in m2.

The existing way of approaching this problem in F# is to write your own. The naive implementation looks something like:

let merge m1 m2 =
    Map.fold (fun s k v -> Map.add k v s) m1 m2

Pros and Cons

The advantages of making this adjustment to F# are it is a handy function and other languages have it. Also it would most likely be more efficient than the implementation above. I'd be happy to do the work should it be accepted.

The disadvantages of making this adjustment to F# are - how key conflicts are dealt with may not be obvious to new users without reading the documentation.

Extra information

Estimated cost (S):

Related suggestions: (put links to related suggestions here)

Affadavit (must be submitted)

Please tick this by placing a cross in the box:

  • [x] This is not a question (e.g. like one you might ask on stack overflow) and I have searched stack overflow for discussions of this issue
  • [x] I have searched both open and closed suggestions on this site and believe this is not a duplicate
  • [x] This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.

Please tick all that apply:

  • [x] This is not a breaking change to the F# language design
  • [x] I would be willing to help implement and/or test this
  • [ ] I or my company would be willing to help crowd-fund F# Software Foundation members to work on this

kjnilsson avatar Apr 13 '17 17:04 kjnilsson

You could go slightly further and parameterise handling the key conflict:

val merge : ('key -> 'value -> 'value -> 'value) -> Map<'key,'value> -> Map<'key,'value> -> Map<'key,'value>

This has the nice property that if ('s, add) is a semigroup (that is, a set 's together with an associative operation add : 's -> 's -> 's), then Map<'key, 's> becomes a semigroup with operation (Map.merge (fun _ -> add)).

I find myself writing this sort of thing all the time, so I'd love to have it on the standard API.

paul43 avatar Apr 16 '17 11:04 paul43

I'd like to see @paul43's idea, if implemented, given the name mergeWith, while Map.merge remains the equivalent of calling Map.mergeWith Map.add since that's the most commonly-desired scenario.

rmunn avatar Apr 17 '17 04:04 rmunn

there's already Map.union in FSharpx.Collections (and there will soon be Map.unionWith too)

sideeffffect avatar Aug 06 '17 03:08 sideeffffect

Would be awesome to have this in the standard library :+1:

wklm avatar Feb 09 '18 13:02 wklm

While porting some ocaml code to F#, I ended up writing Map.merge the way it's written in ocaml. The non-optimized implementation I went with:

let merge (f: 'key -> 'a option -> 'b option -> 'c option) (m1: Map<'key, 'a>) (m2: Map<'key, 'b>) : Map<'key, 'c> =
    let k1 = Set.ofList (keys m1)
    let k2 = Set.ofList (keys m2)
    let keys = Set.union k1 k2
    keys
    |> Seq.choose (fun key ->
        let o1 = Map.tryFind key m1
        let o2 = Map.tryFind key m2
        f key o1 o2
        |> Option.map (fun value -> key, value)
    )
    |> Map.ofSeq

FrankBro avatar Oct 15 '18 00:10 FrankBro

There's already an implementation for both Map.union and Map.unionWith here in F#+. Also for Dictionaries and ReadOnlyDictionaries.

I prefer to leave the name merge for the applicative operation.

gusty avatar Oct 15 '18 05:10 gusty

As another case point, Python got a new operator for Map merges just a few months back: https://docs.python.org/3/whatsnew/3.9.html#dictionary-merge-update-operators

Their rationale shows interesting considerations: https://www.python.org/dev/peps/pep-0584/#rationale

ryepesg avatar Mar 21 '21 01:03 ryepesg

This should be in the Map module from the very beginning. Also the implementation with reversed merging order is useful when you need to set the default values for the keys that missing in original map. Call it Map.mergeBack or Map.default:

someSeq
|> Seq.groupBy(...)
|> Map.ofSeq
|> Map.default(*mergeBack*) <| Map [<someKey>, <some default value if its missing in the group keys>; ...]

konst-sh avatar Jun 11 '23 05:06 konst-sh

I would really like this!

ErikSchierboom avatar Feb 22 '24 13:02 ErikSchierboom