[erlang-questions] lists:mapfind?

Richard A. O'Keefe ok@REDACTED
Mon Aug 21 00:46:37 CEST 2017



On 18/08/17 9:23 PM, Lukas Larsson wrote:
> I've been thinking of adding that and also lists:mapsort, and while at
> it we should probably also add lists:mapdelete, lists:mapmap and the others.
>
> The reason I haven't spent much effort in adding them is because I'm not
> sure the lists module is the correct place to put them and also I'm
> unsure about their general usefulness.

Three observations:

  (1) lists: is absolutely the wrong place for them.
      They belong in the maps: module, where you can find
      maps:filter(Pred, Map1) -> Map2
 >>>> maps:find(Key, Map) -> {ok,Value} | error
      maps:fold(Fun, Init, Map) -> Acc
      ...
      Anybody who wants these functions is most certainly
      going to look for them there first.

      Putting the functions in maps: means you don't have
      to stick 'map' in their names.

  (2) The names give me only the vaguest idea of what the
      functions are supposed to do.  For example, how is
      mapdelete different from maps:take/2?  lists:delete/2
      removes the first copy of its first argument; for
      maps, does this look for a value, a key, or a pair?
      How would lists:mapfind have differed from maps:find/2?

  (3) Given that maps:to_list(Map) -> [{Key,Value}]
      and maps:from_list(List_Of_Pairs) -> Map
      exist, additional functions on maps can be prototyped
      by anyone who wants them in plain Erlang.

      Presumably

      map(Fun, Map) ->
          maps:from_list([{K,Fun(K,V)} || {K,V} <- maps:to_list(Map)]).

      Let's try three variants of delete/2.

      keydelete(Key, Map) ->
          case maps:take(Key, Map)
            of {_,Map1} -> Map1
             ; error    -> Map
          end.

      valuedelete(Value, Map) ->

      Sorry, the definition just fell apart.  In a map, which is
      the "first" pair with a matching value?  Something that
      deletes *all* pairs with matching values, no problem.
      Just use filter.  But the first?

      pairdelete(Key, Value, Map) ->
          maps:filter(fun (K, V) -> K == Key and V == Value end, Map).

It would be a very good thing when proposing a new library
function to provide

  - a specification of what it should do

  - ideally, a reference implementation in Erlang

  - a use case.

Sometimes, of course, it would be very difficult to write the
reference implementation, and sometimes the point of proposing
a new function is to discuss what its specification should be.

Ah.  It just occurred to me that maybe lists:mapfind was meant
to be lists:mapfind/3 with an interface like

     mapfind(Value, Key, MapList) ->
         the first Map in MapList having a pair Key := Value
         or false if there is no such Map.

     % in maps:, so no maps: prefixes.

     find_in_list(Value, Key, [Map|MapList]) ->
         case find(Key, Map)
           of {ok, Value} -> Map
            ; error       -> find_in_list(Value, Key, MapList)
         end;
     find_in_list(_, _, []) ->
         false.

But I am left wondering why this particular combination is so
important.  Suppose instead we add this function to lists:.

%   first(Pred, List) -> {ok, Value} | error
%   Types
%      Value = T
%      List = [T]
%      Pred = fun(T -> boolean())
%
%   If there is a Value in List such that Pred(Value),
%   returns {ok,Value} for the first such Value,
%   otherwise returns error.

first(Pred, [X|Xs]) ->
     case Pred(X)
       of true  -> {ok,X}
        ; false -> first(Pred, Xs)
     end;
first(_, []) ->
     error.

Now find_in_list(Value, Key, MapList) ->
         lists:first(fun (Map) ->
             maps:is_key(Key, Map) andalso maps:get(Key, Map) == Value
          end, MapList).

And now we see the primitive we really want:

%   has_key(Key, Map, Value) -> boolean()
%   Types
%       Key = term()
%       Map = #{}
%       Value = term()
%   Returns true when Map contains Key as a key and associates
%   Value with that key, otherwise false.
%   The call fails with a {badmap,Map} exception if Map is not a map.

maps:has(Key, Map, Value) ->
     is_key(Key, Map) andalso get(Key, Map) == Value.

With that,
     find_in_list(Value, Key, MapList) ->
         lists:first(fun(Map) -> maps:has(Key, Map, Value) end, MapList).

I have found (the equivalent of) maps:has_key/3 very useful when
processing SGML and XML.  I have found (the equivalent of)
lists:first/2 so useful elsewhere that I was surprised to find it
missing from lists:.




More information about the erlang-questions mailing list