add maps:merge_deep/2
deep-merge function was proposed in https://github.com/erlang/otp/pull/2797, but not yet implemented.
I'd question how useful this is. I think in practice, a function exactly like this won't be particularly useful - frequently you'd want to do more special handling for different values - e.g. concatenating lists, and similar. Given a user-defined implementation (without the strict & specific error handling of the maps module) now that we have merge_with is very simple, I'm not sure it makes sense to include it.
deep_merge(Map1, Map2) ->
Combine = fun
(_Key, Value1, Value2) when is_map(Value1), is_map(Value2) -> deep_merge(Value1, Value2);
(_Key, _Value1, Value2) -> Value2
end,
maps:merge_with(Combine, Map1, Map2).
Please see a discussion on adding exactly the same function to Elixir's Map module: https://github.com/elixir-lang/elixir/pull/5339
@michalmuskala I personally don't think this is a niche feature, because there should not be so many cases that needs special handling, other than what you refer to as list concatenation. Unlike Elixir, we don't have struct, thus there is no need to figure out how to merge across types. So if we can select whether two lists with the same key are concatenated or not, it can be useful for everyone.
I think in practice, a function exactly like this won't be particularly useful - frequently you'd want to do more special handling for different values - e.g. concatenating lists, and similar.
Its exactly the same as :maps.merge but unravels nests. In practise that is what you want, often when working with nested merge-able data there are no lists involved, everything is a map (a list of users, becomes a map of users by their uuid). If you are thinking how to merge concat lists you are giving yourself an unneeded headache for an unusual use-case.
Please see a discussion on adding exactly the same function to Elixir's
Mapmodule: elixir-lang/elixir#5339
No structs to deal with. Also put_in is still broken on Elixir (does not work with structs) and does not give any stacktrace, so I think the non-struct Map related usage of the lang is lagging.
Also if I had a dollar for everytime I seen..
user
address = user.address
street = user.address.street
unit = user.address.street.unit
%{ user | address:
%{ address | street:
%{street | unit:
%{ unit | no: 5}
}
}
}
#VS
:maps.merge_deep(user, %{address: %{street: %{unit: %{no: 5}}}})
I like this feature and have written the equivalent to this function is a number of places already. Though I would like us to think a bit in a broader context before merging this.
The question that comes to my mind is if merge is the only function that should have a deep variant? i.e. do we want maps:deep_put/deep_get? and if so, should this be in a completely different module?
@garazdawi: That also touches on the ideas about lenses that Björn-Egil mentioned way back when maps were introduced.
@garazdawi I would say deep_get/deep_put are nice-to-have. Let me explain it with an example; some config files have nested-key syntax, which looks likefoo.bar = x. When we want to parse these inputs and transform it to map in Erlang, one can write deep_put as below and utilize it:
deep_put([Key], Val, Map) ->
merge(Key, Val, Map);
deep_put([Key|More], Val, Map) ->
merge(Key, deep_put(Paths, Val, #{}), Map).
That also touches on the ideas about lenses that Björn-Egil mentioned way back when maps were introduced.
Yes, this comes close to that, though it would not be as general. Having something like lenses would be great, though I don't think we ever got to a place where we had a proposal that we were happy with.
some config files have nested-key syntax
It is also mostly when working with config that I have used the deep maps functions.
Shameless plug for a maps companion module I’ve written and used extensively: mapz
The idea was to “one day” submit it as a PR to the original module, hence the awkward name. It’s reasonably tested and even more well exercised in production. Perhaps it contains some inspiration for additional functions that would make sense or as a additional point of view to discussions around choices (keys, “paths”, errors etc.) when it comes to nested maps.
I’d be happy to complement this PR with additional functions from my implementation or submit it as another PR if desired.
@michalmuskala One example of allowing special handling for other data types can be seen in the deep_merge_with implementation which can handle collisions with a custom function.
CT Test Results
Tests are running... https://github.com/erlang/otp/actions/runs/2015752244
Results for commit 98ed0b4ccdd03a77785dad358a1d6f9bdfa01a57
To speed up review, make sure that you have read Contributing to Erlang/OTP and that all checks pass.
See the TESTING and DEVELOPMENT HowTo guides for details about how to run test locally.
Artifacts
- No CT logs found
- No HTML docs found
- No Windows Installer found
// Erlang/OTP Github Action Bot