-
Notifications
You must be signed in to change notification settings - Fork 3.4k
[Proposal] Implement Map.deep_merge/2 and Map.deep_merge/3 #5339
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
* as proposed in https://groups.google.com/forum/#!topic/elixir-lang-core/ak3kVqJ4-8g but with a concrete implementation to reason about :)
@@ -24,7 +24,7 @@ defmodule KeywordTest do | |||
|
|||
test "implements (almost) all functions in Map" do | |||
assert Map.__info__(:functions) -- Keyword.__info__(:functions) == | |||
[from_struct: 1] | |||
[deep_merge: 2, deep_merge: 3, from_struct: 1] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as mentioned before, I know that this is less than ideal but I wanna get the general deep_merge through before tackling it for Keyword
@@ -474,6 +474,77 @@ defmodule Map do | |||
end | |||
|
|||
@doc """ | |||
Merges two maps similar to `Map.merge/2`, but with the important difference |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first line of the documentation should always be a short summary.
I am still on the fence. It feels the most common use case would be something that knows how to merge across different data-types. I am particular biased because I have only needed deep_merge twice and it was once for Phoenix and once for Elixir. |
There are definitely a lot of use cases for deep merge, we use it heavily We have both Map and Keyword deep_merge/2. We don't do list merges Plus I just commited a patch to Absinthe to fix a deep merge problem: Maybe Enum.deep_merge is apt, though it would be hard to do stuff like |
I didn't comment in the mailing list discussion as I got feeling that people agreed that it could be useful but looks limited and doesn't seem like it belongs to the stdlib. |
@lexmag We handle merging structs in our library ;) |
This is just a draft, I wasn't aware of a problem with structs (which is my bad) but that would most certainly be fixable. I'm of course also open to write another implementation that lives in Personally I used Of course, I also understand that it is not the most common method overall and as I said perfectly fine with it if you say it's not stdlib worthy and people should write it themselves/rely on some other source :) Thanks for the discussion! |
Sorry for being lazy and not checking the source but how was it solved? In my mind the correct way would be to use a protocol? |
@josevalim So it's pretty basic I detect a struct and create an empty struct of the same kind then remove the default values from the map that needs to be merged. So then we have a clean map to merge with the struct. We also remove the If you want to set a variable to default the you just do that outside of the merge. |
@josevalim I think protocols are a good solution, but the map one would just have to be aware of structs. |
@olafura yup. Every implementation would need to have a logic that checks if two things are of the same type before invoking the protocol. The protocol could also be implemented for Any with a default of taking the second value as is. I believe though there are still too many questions to be answered before moving forward. Using protocols seems to be the most correct but it feels a very niche feature to justify adding a new protocol to the language. |
@josevalim I'm wondering if it would be good to do a staging like library for stdlib then we can just see how many people are using those staging functions roughly (we wouldn't see some private code bases like ours). I know that there is Experimental, maybe it would be a good candidate? |
I don't believe it needs to be anything special for stdlib, just a regular library is fine. There is no reason to Experimental as well, it is just a random namespace after all. The only reason we used Experimental for GenStage was to avoid naming conflicts when it is merged into Elixir but as long as you pick a sane namespace, like DeepMerge, it should be fine. It is unlikely we would add it to Elixir as DeepMerge. |
For anyone who may be stumbling upon this, I took the feedback from here and implemented it in the deep_merge library.
|
This was first proposed in https://groups.google.com/forum/#!topic/elixir-lang-core/ak3kVqJ4-8g
People seemed generally in favor of it but the need for some adjustable behavior was made clear quickly as people had differing requirements (what about lists etc.?), hence there was also a need for
Map.deep_merge/3
.To my knowledge (not knowing all the core members by heart) there was no green light by elixir-core members for this feature but some rather popular devs agreed. The discussions got a lot into the implementation/API details so I figured drafting up an implementation was the best course of action to see if this was truly a good idea (due to some delays it took me until now to finish up a half done implementation).
Please don't feel compelled to just accept it cause someone made the work. I'm perfectly aware this might be rejected and in that case will create a deep_merge hex package. I'm also aware, that for this to be fully fledged there should be a Keyword list version but only wanted to implement that once the Map version/general idea is green lit :)
Implementation Details + Performance
I decided to implement
deep_merge/2
in terms ofdeep_merge/3
- had a specific implementation for it (calleddeep_merge_specific
in the benchmark) that was only slightly faster (you can look it up here) so I decided for the easier to maintain version.Benchmark code (from my elixir_playground repo - basically a map with 50 keys + some deeply nested structures:
Results:
As one can see,
deep_merge
is a lot slower thanmerge/2
but that is largely due to the fact thatmerge/3
is a lot slower thanmerge/2
(that's why it's included in the benchmark but with the same behaviour asmerge/2
) -merge/2
just delegates to Erlang whilemerge/3
is a custom implementation that we can probably improve upon (in some other PR).Ideas + feedback welcome :)