-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Add ability for hierarchical grouping to maps:groups_from_list
#8830
base: master
Are you sure you want to change the base?
Conversation
CT Test Results 2 files 95 suites 55m 33s ⏱️ Results for commit 038e924. ♻️ This comment has been updated with latest results. 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// Erlang/OTP Github Action Bot |
Co-authored-by: Jan Uhlig <[email protected]>
d192c09
to
038e924
Compare
I am not really sure this function belongs in stdlib, as it aims to solve only a very particular use case, where telling folks to reach out to a particular library or a custom solution would be completely fine. For example, I can't recall similar functionality in no other data types of other standard libraries. If we really want to go down this route, then I would suggest rather having maps:groups_from_list(
fun(#{continent := Continent}) -> Continent end,
fun(X) -> X end,
fun(Child) ->
maps:groups_from_list(
fun(#{country := Country}) -> Country end,
fun(#{city := City}) -> City end,
Child
)
end, Data). This should allow more use cases but even then I am not convinced it is worth it. :( |
The function already exists, this PR is only an extension. It does not change the current behavior, no existing code needs to be changed.
AFAIK,
This looks much more complicated 🫤 tl;dr: lets see what the OTP team thinks 😉 |
I was mostly asking about other programming languages that offer such grouping as part of their standard libraries, to use it as reference. In any case, we have had gb_trees, dict, orddict, etc for decades and it seems the use case has not been common enough to warrant similar requests. The main concern is that we added this particular function to the It is easy for standard library modules to do too much if they attempt to solve every possible problem. But I'd say that's not their job. Which practical examples could this function be applied to? Erlang/OTP is a large codebase, perhaps there are examples of where this function would be useful in Erlang/OTP itself? |
Well, in the light of a recent PR ( 😁 ),
It doesn't do multiple things, IMO. It does the same thing (that is, grouping), only adding the ability to group on multiple levels. And wouldn't your suggestion of a separate group function add the "does multiple [different] things" part?
Frankly, I don't know 🤷♀️ (and I think it is asking a bit much of me 😅). I do have my own use cases, several, for which I gave an (necessarily abstracted) example. Admittedly, they are somewhat removed from usages in OTP itself; and if this PR gets rejected, I can as well go with an own implementation, so I don't exactly mind 😜 That said, there are 26 usages of I guess what I'm trying to say is, this function is probably useful in external stuff (like, mine), and not in OTP itself (which doesn't make big use of even the single-level grouping). |
Right, but when you do a search in Erlang/OTP for Lines 52 to 53 in 0c8c61a
So it is much easier to argue for its benefits. And others can share their experience too. In any case, I am not asking you to answer of all the questions above. You are 100% right in submitting a pull request 🚀, I am just adding my $.02. Ultimately it is not our decision to make. :) |
Agree, let's see what others think :) |
This PR is a suggestion for an extension to
maps:groups_from_list/2,3
to create nested groups, or a group hierarchy (also see here).With this change,
maps:groups_from_list/2,3
will also accept lists of instead of only a single key function. The result, accordingly, is a hierarchy of groups, with the keys and groupings on each tier given by the respective key function.Thus, it is easy to turn, for example, a list of data rows (as may be obtained from a database), like this:
... into a structure like this:
While something like this is also doable with the current implementation of
maps:groups_from_list/2,3
alone, it involves a significant amount of boilerplate code, which is tiresome and error-prone to write as well as hard to read, understand, and change. Having done this a couple of times in the past, I was thinking that maybe there should be a general out-of-the-box way to do it.Another performance-related problem with repeated calls to
maps:groups_from_list/2,3
(usually viamaps:map
calls for hierarchical groupings) is that each of those calls results in a list reversal, in order to return the grouped elements in the same order as in the input list. However, from the outside perspective, there is at most 1 reversal required, namely when the number of sub-groupings (ie, the number of tiers in the result hierarchy) is odd: two subsequent reversals will cancel each other out.The proposal here addresses both of the problems above: no boilerplate code necessary, just give a list of key functions; reduce the number of list reversals to the required minimum, either one or zero.
The implementation we present here is a bit cumbersome. This is because of the way errors have to be for
erl_stdlib_errors
to work. That is, an error related to invalid input (like, an element of theKeyFuns
list is not a 1-ary function, or the list to group on is not a proper list) must originate from themaps:groups_from_list/2,3
call. At the same time, errors happing in any of the given key or value functions must be passed through.This culminates in a few peculiarities;
maps:groups_from_list([], List)
succeeds and returnsList
if it is a list, that is, even if it is an improper list; at the same time, a similar call likemaps:groups_from_list([], ValueFun, List)
succeeds and returnsList
mapped byValueFun
if and only ifList
is a proper list, and fails ifList
is not a proper list.maps:from_list/2,3
, when the givenList
is an improper list, will fail early when the list of key functions contains an odd number of elements, but will fail only after the first iteration over the givenList
(ie, the generation of the first tier) if the list of key functions contains an even number of elements.