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.