Skip to content

Commit

Permalink
Add deep_update_with/4
Browse files Browse the repository at this point in the history
  • Loading branch information
eproxus committed Jul 2, 2020
1 parent 2abc9ef commit cbe0364
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 27 deletions.
14 changes: 12 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,19 @@
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
and this project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [2.1.0] - 2020-07-02

### Added

- New [`deep_update_with/4`][deep_update_with-4] function

[deep_update_with-4]: https://hexdocs.pm/mapz/mapz.html#deep_update_with-4

## [2.0.0] - 2019-11-27

### Added
Expand Down Expand Up @@ -41,7 +50,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

Initial release

[unreleased]: https://github.com/eproxus/mapz/compare/v2.0.0...HEAD
[unreleased]: https://github.com/eproxus/mapz/compare/v2.1.0...HEAD
[2.1.0]: https://github.com/eproxus/mapz/compare/v2.0.0...v2.1.0
[2.0.0]: https://github.com/eproxus/mapz/compare/v1.0.0...v2.0.0
[1.0.0]: https://github.com/eproxus/mapz/compare/v0.3.0...v1.0.0
[0.3.0]: https://github.com/eproxus/mapz/releases/tag/v0.3.0
2 changes: 1 addition & 1 deletion src/mapz.app.src
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{application, mapz, [
{description, "Extensions to the Erlang maps module"},
{vsn, "2.0.0"},
{vsn, "2.1.0"},
{applications, [kernel, stdlib]},

{licenses, ["MIT"]},
Expand Down
56 changes: 36 additions & 20 deletions src/mapz.erl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
-export([deep_put/3]).
-export([deep_update/3]).
-export([deep_update_with/3]).
-export([deep_update_with/4]).
-export([deep_remove/2]).
-export([deep_merge/1]).
-export([deep_merge/2]).
Expand Down Expand Up @@ -88,15 +89,9 @@ deep_get(Path, Map, Default) ->
-spec deep_put(path(), term(), map()) -> map().
deep_put(Path, Value, Map1) ->
check(Path, Map1),
update(Map1, Path,
fun(_Existing) -> Value end,
fun
(P, _Rest, {ok, _Existing}) ->
error({badvalue, P});
(_P, Rest, error) ->
lists:foldr(fun(Key, Acc) -> #{Key => Acc} end, Value, Rest)
end
).
update(Map1, Path, fun(_Existing) -> Value end, fun(P, Rest, V) ->
badvalue_and_create(P, Rest, V, Value)
end).

% @doc If `Path' exists in `Map1', the old associated value is replaced by value
% `Value'. The function returns a new map `Map2' containing the new associated
Expand All @@ -113,13 +108,7 @@ deep_put(Path, Value, Map1) ->
-spec deep_update(path(), term(), map()) -> map().
deep_update(Path, Value, Map1) ->
check(Path, Map1),
update(Map1, Path,
fun(_Existing) -> Value end,
fun
(P, _Rest, {ok, _Existing}) -> error({badvalue, P});
(P, _Rest, error) -> error({badkey, P})
end
).
update(Map1, Path, fun(_Existing) -> Value end, fun badvalue_and_badkey/3).

% @doc Update a value in a `Map1' associated with `Path' by calling `Fun' on the
% old value to get a new value.
Expand All @@ -135,14 +124,32 @@ deep_update(Path, Value, Map1) ->
% </ul>
-spec deep_update_with(path(), fun((term()) -> term()), map()) -> map().
deep_update_with(Path, Fun, Map1) ->
deep_update_with_1(Path, Fun, Map1, fun badvalue_and_badkey/3).

% @doc Update a value in a `Map1' associated with `Path' by calling `Fun' on the
% old value to get a new value. If `Path' is not present in `Map1' then `Init'
% will be associated with `Path'.
%
% The call can raise the following exceptions:
% <ul>
% <li>`{badmap,Map}' if `Map1' is not a map</li>
% <li>`{badpath,Path}' if `Path' is not a path</li>
% <li>`{badvalue,P}' if a term that is not a map exists as a intermediate key at
% the path `P'</li>
% <li>`badarg' if `Fun' is not a function of arity 1</li>
% </ul>
-spec deep_update_with(path(), fun((term()) -> term()), any(), map()) -> map().
deep_update_with(Path, Fun, Init, Map1) ->
deep_update_with_1(Path, Fun, Map1, fun(P, Rest, Value) ->
badvalue_and_create(P, Rest, Value, Init)
end).

deep_update_with_1(Path, Fun, Map1, Default) ->
check(Path, Map1),
check_fun(Fun, 1),
update(Map1, Path,
fun(Value) -> Fun(Value) end,
fun
(P, _Rest, {ok, _Existing}) -> error({badvalue, P});
(P, _Rest, error) -> error({badkey, P})
end
Default
).

% @doc Removes the last existing key of `Path', and its associated value from
Expand Down Expand Up @@ -287,3 +294,12 @@ remove(Map, [First, Second|Path]) when is_map(Map) ->
error ->
Map
end.

create(Path, Value) ->
lists:foldr(fun(Key, Acc) -> #{Key => Acc} end, Value, Path).

badvalue_and_badkey(P, _Rest, {ok, _Existing}) -> error({badvalue, P});
badvalue_and_badkey(P, _Rest, error) -> error({badkey, P}).

badvalue_and_create(P, _Rest, {ok, _Existing}, _Init) -> error({badvalue, P});
badvalue_and_create(_P, Rest, error, Init) -> create(Rest, Init).
29 changes: 25 additions & 4 deletions test/mapz_tests.erl
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
deep_put/3,
deep_update/3,
deep_update_with/3,
deep_update_with/4,
deep_remove/2,
deep_merge/1,
deep_merge/2,
Expand Down Expand Up @@ -84,14 +85,34 @@ deep_update_with_test_() ->
?_assertExit(badarg, deep_update_with([a], fun() -> foo end, ?STRUCT))
] ++ error_(fun(P, M) -> deep_update_with(P, Incr, M) end)}.

error_(Function) ->
deep_update_with_init_test_() ->
Incr = fun(V) -> V + 1 end,
{inparallel, [
?_assertEqual(#{a => 2}, deep_update_with([a], Incr, 0, #{a => 1})),
?_assertEqual(
deep_put([a, a, a], 2, ?STRUCT),
deep_update_with([a, a, a], Incr, 0, ?STRUCT)
),
?_assertEqual(
deep_put([a, a, x, y], 0, ?STRUCT),
deep_update_with([a, a, x, y], Incr, 0, ?STRUCT)
),
?_assertExit(badarg, deep_update_with([a], x, 0, ?STRUCT)),
?_assertExit(badarg, deep_update_with([a], fun() -> foo end, 0, ?STRUCT))
] ++ error_init_(fun(P, M) -> deep_update_with(P, Incr, 0, M) end)}.

error_(Function) ->
[
?_assertError({badkey, [b]}, Function([b], #{a => 1})),
?_assertError({badkey, [b, x]}, Function([b, x], ?STRUCT))
] ++ error_init_(Function).

error_init_(Function) ->
[
?_assertError({badmap, foobar}, Function([a], foobar)),
?_assertError({badpath, foobar}, Function(foobar, #{a => 1})),
?_assertError({badkey, [b]}, Function([b], #{a => 1})),
?_assertError({badkey, [b, x]}, Function([b, x], ?STRUCT)),
?_assertError({badvalue, [d]}, Function([d, x], ?STRUCT))
]}.
].

deep_remove_test_() ->
{inparallel, [
Expand Down

0 comments on commit cbe0364

Please sign in to comment.