From 0929d82980f1ee81648dabdda719594d7f831a76 Mon Sep 17 00:00:00 2001 From: Paul Guyot Date: Sun, 27 Aug 2023 10:01:07 +0200 Subject: [PATCH] Implement maps:foreach/2 Signed-off-by: Paul Guyot --- libs/estdlib/src/maps.erl | 35 ++++++++++++++++++++++++++++++ tests/libs/estdlib/test_maps.erl | 37 ++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/libs/estdlib/src/maps.erl b/libs/estdlib/src/maps.erl index 826aad155..896b6dac1 100644 --- a/libs/estdlib/src/maps.erl +++ b/libs/estdlib/src/maps.erl @@ -50,6 +50,7 @@ find/2, filter/2, fold/3, + foreach/2, map/2, merge/2, remove/2, @@ -335,6 +336,33 @@ fold(_Fun, _Init, Map) when not is_map(Map) -> fold(_Fun, _Init, _Map) -> error(badarg). +%%----------------------------------------------------------------------------- +%% @param Fun function to call with every key-value pair +%% @param MapOrIterator the map or map iterator over which to iterate +%% @returns `ok' +%% @doc Iterate over the entries in a map. +%% +%% This function takes a function used to iterate over all entries in a map. +%% +%% This function raises a `badmap' error if `Map' is not a map or map iterator, +%% and a `badarg' error if the input function is not a function. +%% @end +%%----------------------------------------------------------------------------- +-spec foreach( + Fun :: fun((Key :: key(), Value :: value()) -> any()), + MapOrIterator :: map_or_iterator() +) -> ok. +foreach(Fun, Map) when is_function(Fun, 2) andalso is_map(Map) -> + iterate_foreach(Fun, maps:next(maps:iterator(Map))); +foreach(Fun, [Pos | Map] = Iterator) when + is_function(Fun, 2) andalso is_integer(Pos) andalso is_map(Map) +-> + iterate_foreach(Fun, maps:next(Iterator)); +foreach(_Fun, Map) when not is_map(Map) -> + error({badmap, Map}); +foreach(_Fun, _Map) -> + error(badarg). + %%----------------------------------------------------------------------------- %% @param Fun the function to apply to every entry in the map %% @param Map the map to which to apply the map function @@ -463,6 +491,13 @@ iterate_fold(Fun, {Key, Value, Iterator}, Accum) -> NewAccum = Fun(Key, Value, Accum), iterate_fold(Fun, maps:next(Iterator), NewAccum). +%% @private +iterate_foreach(_Fun, none) -> + ok; +iterate_foreach(Fun, {Key, Value, Iterator}) -> + _ = Fun(Key, Value), + iterate_foreach(Fun, maps:next(Iterator)). + %% @private iterate_map(_Fun, none, Accum) -> Accum; diff --git a/tests/libs/estdlib/test_maps.erl b/tests/libs/estdlib/test_maps.erl index a2f83bae5..81234ef59 100644 --- a/tests/libs/estdlib/test_maps.erl +++ b/tests/libs/estdlib/test_maps.erl @@ -37,6 +37,7 @@ test() -> ok = test_find(), ok = test_filter(), ok = test_fold(), + ok = test_foreach(), ok = test_map(), ok = test_merge(), ok = test_remove(), @@ -143,6 +144,42 @@ test_fold() -> ?ASSERT_ERROR(maps:fold(not_a_function, any, maps:new()), badarg), ok. +collect_foreach(Acc) -> + Self = self(), + receive + {Self, Key, Value} -> + collect_foreach([{Key, Value} | Acc]) + after 0 -> Acc + end. + +test_foreach() -> + % maps:foreach/2 was introduced with OTP 24. + HasForeach = + case erlang:system_info(machine) of + "BEAM" -> erlang:function_exported(maps, foreach, 2); + "ATOM" -> true + end, + if + HasForeach -> + Self = self(), + Fun = fun(Key, Value) -> Self ! {self(), Key, Value} end, + ok = maps:foreach(Fun, maps:new()), + ?ASSERT_EQUALS(collect_foreach([]), []), + ok = + receive + {Self, _, _} -> fail + after 0 -> ok + end, + ok = maps:foreach(Fun, #{a => 1, b => 2, c => 3}), + ?ASSERT_EQUALS(lists:sort(collect_foreach([])), [{a, 1}, {b, 2}, {c, 3}]), + ok = check_bad_map(fun() -> maps:foreach(Fun, id(not_a_map)) end), + ok = check_bad_map_or_badarg(fun() -> maps:foreach(not_a_function, id(not_a_map)) end), + ?ASSERT_ERROR(maps:foreach(not_a_function, maps:new()), badarg), + ok; + true -> + ok + end. + test_map() -> Fun = fun(_Key, Value) -> 2 * Value end, ?ASSERT_EQUALS(maps:map(Fun, maps:new()), #{}),