Using Erlang's =maps:groups_from_list/2= function

Since OTP 25, Erlang has included a function maps:groups_from_list/2 and I had occasion to use it recently.

In KAZOO, we have a process that monitors other processes, keeping track of their message queue length, heap size, etc (we're on OTP 26 at the moment so need to do this manually).

I wanted to print a table of the tracked processes, ordered first by message queue length, but within the group, sorted by heap size.

-record(proc_info, {pid, message_queue_len, total_heap_size}).

sort_processes(Processes) ->
    %% Groups = #{mql => [#proc_info{}...]
    Grouped = maps:groups_from_list(fun(#proc_info{message_queue_len=MQL}) -> MQL end
				   ,Processes
				   ),
    maps:fold(fun sort_group_by_heap/3, [], Grouped).

sort_group_by_heap(_MQL, ProcInfos, Acc) ->
    lists:keysort(#proc_info.total_heap_size, ProcInfos) ++ Acc.

The first argument is used to extract the each list element a "key" to use when inserting the element into the resulting map. In my case, the grouping was for message_queue_len.

The Grouped map() will contain keys corresponding to the unique set of message queue lengths, and the value will be the list of #proc_info{} records.

There is an arity-3 version where you can map the list element into a new value to be put into the grouping's list of results as well. As I want to sort the records by total_heap_size after grouping, I had no cause to use it.

What I like about Erlang records

Records get a bad rap in Erlang, especially now that maps exist. I find the utility of records to be so great that I reach for them way more often than for maps when I have a fixed amount of fields. Records work really well with the lists:key* functions to, providing the index into the record tuple as above in sort_group_by_heap.

One day I'll write more about how I use records but wanted to mention this specific use as a common one in my work.