otp icon indicating copy to clipboard operation
otp copied to clipboard

add maps:merge_deep/2

Open 6293 opened this issue 5 years ago • 11 comments

deep-merge function was proposed in https://github.com/erlang/otp/pull/2797, but not yet implemented.

6293 avatar Dec 28 '20 16:12 6293

CLA assistant check
All committers have signed the CLA.

CLAassistant avatar Dec 28 '20 16:12 CLAassistant

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 avatar Jan 04 '21 13:01 michalmuskala

@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.

6293 avatar Jan 04 '21 14:01 6293

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 Map module: 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}}}})

vans163 avatar Jan 05 '21 06:01 vans163

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 avatar Jan 19 '21 09:01 garazdawi

@garazdawi: That also touches on the ideas about lenses that Björn-Egil mentioned way back when maps were introduced.

RaimoNiskanen avatar Jan 19 '21 09:01 RaimoNiskanen

@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).

6293 avatar Jan 19 '21 09:01 6293

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.

garazdawi avatar Jan 19 '21 10:01 garazdawi

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.

eproxus avatar Oct 21 '21 20:10 eproxus

@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.

eproxus avatar Oct 22 '21 06:10 eproxus

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

github-actions[bot] avatar Mar 21 '22 11:03 github-actions[bot]