Skip to content

Conversation

@beb63
Copy link

@beb63 beb63 commented Jan 2, 2026

These seven new functions in the lists module are aligned with the semantics and principles of the corresponding existing key search functions for lists with tuples. A map with an arbitrary number of association pairs is used as search key. The first element in the list that is a map and contains association pairs comparing equal to all of the key's association pairs is considered to match the key. When an element in the list matches the key, the key search function performs the list operation it is set out to do and returns the result. If no element matches the key, the list is left unchanged in the return value or 'false' is returned.

@CLAassistant
Copy link

CLAassistant commented Jan 2, 2026

CLA assistant check
All committers have signed the CLA.

@github-actions
Copy link
Contributor

github-actions bot commented Jan 2, 2026

CT Test Results

Tests are running... https://github.com/erlang/otp/actions/runs/20666261664

Results for commit 3e795ab

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

These seven new functions in the lists module are aligned with the semantics
and principles of the corresponding existing key search functions for lists
with tuples. A map with an arbitrary number of association pairs is used as
search key. The first element in the list that is a map and contains
association pairs comparing equal to all of the key's association pairs is
considered to match the key. When an element in the list matches the key, the
key search function performs the list operation it is set out to do and
returns the result. If no element matches the key, the list is left unchanged
in the return value or 'false' is returned.
@beb63 beb63 force-pushed the stdlib/lists-add-key-search-functions-for-lists-with-maps branch from 3e795ab to 451efda Compare January 2, 2026 23:10
Copy link
Contributor

@Maria-12648430 Maria-12648430 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Useful as your additions may be, there is a general reluctance to add even more functions to the already rather bloated lists module, so I guess this is unlikely to be accepted.

For that matter, it was IMO a (very old) mistake to add functions which operate on lists of specific elements to a module which (nominally at least) deals with lists of anything.

I left some inline comments nonetheless.

That all said, your naming scheme (key*_m) is kind of... off 😅. The _m part meaning "... for maps" appears nowhere else, neither in this nor any other module (that I know of). Also, key* doesn't seem to fit all too well here either, since the functions rather match one map against others instead of their keys.

MapList1 :: [Map],
MapList2 :: [Map],
Map :: map().
keydelete_m(Key, [E | Tail]) when is_map(Key) andalso map_size(Key) > 0 ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
keydelete_m(Key, [E | Tail]) when is_map(Key) andalso map_size(Key) > 0 ->
keydelete_m(Key, [E | Tail]) when is_map(Key) ->

While it may make not much sense to explicitly allow empty maps (which in this case boils down to "remove everything"), I don't think it should be explicitly forbidden.

Same for the other functions you added with your PR.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The semantics of an empty map is actually "match any map" which boils down to "match the first map element in the list". In other words, "remove the first element" in this case.

Either way it makes no sense as you say. I just thought it'd be better to not allow an empty map as a key, but if you still think it should be allowed, maybe it should be described in the documentation?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand the reasoning, but IMO we should not impose restrictions based on what (we think) makes sense or not. Ie, if users want to do it that way, we let them. (This is a doctrine which never failed me, at least 😜)

It would be a different story if we had to do something special/extra work to make pointless things possible, or if having pointless ways of doing things would stand in the way of performance or sth.

That said, before you do any changes based on my suggestions, wait and see if the additions from this PR will be accepted at all. As I said, that is not a given because of current (over-)abundance of functions in the lists module even now. IIRC, I had own and seen other peoples' PRs rejected because of this.

MapList2 :: [Map],
Map :: map().
keydelete_m(Key, [E | Tail]) when is_map(Key) andalso map_size(Key) > 0 ->
try maps:with(maps:keys(Key), E) == Key of
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it is necessary to wrap each iteration step in a try ... catch. Key is already tested for mappyness (is that a word? 🤔), so you only need to test E for mappyness in each iteration step. tl;dr, it may be better to test Key in the (exported) API function, and run the actual keydelete loop in a helper (not exported) function which guards for mappyness of each E.

Same for the other functions you added with your PR.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree.

Comment on lines +1789 to +1817
-doc(#{since => <<"OTP 29.0">>}).
-spec keysort_m(KeyElement, MapList1) -> MapList2 when
KeyElement :: term(),
MapList1 :: [Map],
MapList2 :: [Map],
Map :: map().
keysort_m(KeyElem, MapList) ->
keysort_m(KeyElem, MapList, []).

keysort_m(KeyElem, [E | Tail], AccSort) ->
NewAccSort = keysort_m3(AccSort, E, KeyElem),
keysort_m(KeyElem, Tail, NewAccSort);
keysort_m(_, [], AccSort) ->
AccSort;
keysort_m(_, _, _) ->
erlang:error(badarg).

keysort_m3([SortedE | Tail], E, KeyElem) ->
try maps:get(KeyElem, E) < maps:get(KeyElem, SortedE) of
true ->
[E, SortedE | Tail];
false ->
[SortedE | keysort_m3(Tail, E, KeyElem)]
catch
_ : _ ->
[SortedE | keysort_m3(Tail, E, KeyElem)]
end;
keysort_m3([], E, _) ->
[E].
Copy link
Contributor

@Maria-12648430 Maria-12648430 Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I read this right (ie, I didn't try it out), elements which are not a map are ignored/dropped from the result list. This is different from what keydelete/3 does, which raises an error if an element is not a tuple or not a tuple of sufficient size.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First of all, lists:keydelete/3 ignores anything that does not match the key criteria. No matter which term it is:

119> lists:keydelete(h, 1, [{}, oo, {h,1}]).
[{},oo]

The only restriction is from the spec:

-spec keydelete(Key, N, TupleList1) -> TupleList2 when
      Key :: term(),
      N :: pos_integer(),
      TupleList1 :: [Tuple],
      TupleList2 :: [Tuple],
      Tuple :: tuple().

My key*_m functions have the same spec- and run principles as the existing ones.

This is the sort function which follows that same philosophy. This is a matter of changing the order of elements. If an element does not match the Key, the element shall not change place in the list by itself. Only those elements that match the key specification shall change their positions in relation to the other matching elements. Note that the key in this case is not the same Key as in the other functions. This Key is a map's key element of an association pair. In a homogenous list there should only be maps and all of them should have an association pair where the Key matches. But why not allow the list to contain other elements as well. The sorting algorithm only applies to matching elements. Leave the rest in peace :-)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, I understand the reasoning and motivation. But new functions should follow the semantics of the existing ones, the ones they resemble the most (which, in the case of keysort_m, would be keysort, not keydelete).

I am aware that the semantics of the existing key* functions are inconsistent (keydelete ignores non- and too short tuples as you pointed out, while keysort raises an error). However, we can't change the semantics of the existing functions without running the risk of breaking a lot of existing code out there. Ie, they have been this way since, like, forever 🤷‍♀️

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll be happy to get everything consistent, clean, logical and whatever good there is to attach to these functions in a dialog with the "boss"es of J.A's legacy if a positive decision comes. Until then, I'll just relax and wish for further expansion of the Erlang community.

Comment on lines +38 to +39
keydelete_m/1, keyfind_m/1, keymember_m/1, keyreplace_m/1,
keysort_m/1, keystore_m/1, keytake_m/1,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
keydelete_m/1, keyfind_m/1, keymember_m/1, keyreplace_m/1,
keysort_m/1, keystore_m/1, keytake_m/1,
keydelete_m/1, keyfind_m/1, keymember_m/1, keyreplace_m/1,
keysort_m/1, keystore_m/1, keytake_m/1,

Indendation.

@beb63
Copy link
Author

beb63 commented Jan 5, 2026

These functions are indeed useful. That's why I wrote five of them at first. Then added two more when I realized they could possibly take a place in the lists module.

From one perspective I understand that even the existing key search functions for tuples have a doubtful place in the more general lists module. However, for old sin's sake, I nonetheless propose to add the maplist functions since they are similar to the tuplelist functions. I don't see any better place at this time. These functions are in any case operating on elements in a list regardless of being specialized on certain erlang terms or in general.

The naming scheme derives from the need to separate them from the existing key* functions. Those existing function names does not imply any erlang term specifics even though they operate on one specific erlang term. So the new functions just need to have a separate naming scheme. I chose key*_m, but could instead be keym* or mkey* or something else that is not too long and gives some hint of what they are for. They could of course also have the same names as the existing ones. Most of them would then have one argument less (but not all). I could go for any of those alternatives.

Regarding key vs. match, I'd hold on to the key naming because of the essence of my proposed functions; The fundament for these functions is this:

  • List of maps
  • Each map has one association pair #{Key1 => ValueN} that is a unique identity
  • Each map in the list also has an arbitrary number of other association pairs that are just data (not identity)
  • Later in my project, I got a list of maps where two association pairs #{Key1 => ValueN, Key2 => ValueM} together makes a unique identity
  • The association pair(s) in the Key map are in fact the search key - not the map itself.
  • An element match is not an exact match of the Key - An element matches the key search criteria when all of the Key's association pairs compares equal to (not matching) association pairs in the list element.

@Maria-12648430
Copy link
Contributor

From one perspective I understand that even the existing key search functions for tuples have a doubtful place in the more general lists module. However, for old sin's sake, I nonetheless propose to add the maplist functions since they are similar to the tuplelist functions. I don't see any better place at this time. These functions are in any case operating on elements in a list regardless of being specialized on certain erlang terms or in general.

Well, I agree... That said, let's see what the OTP team thinks 🤷‍♀️

@rickard-green rickard-green added the team:VM Assigned to OTP team VM label Jan 7, 2026
@jhogberg jhogberg self-assigned this Jan 7, 2026
@jhogberg
Copy link
Contributor

Thanks for your pull request! We've decided not to adopt this as-is, since we believe it's more useful to add variants with predicates that would support any kind of data structure in anticipation of native records (OTP 29/30).

@jhogberg jhogberg closed this Jan 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

team:VM Assigned to OTP team VM

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants