From 64617423d513e37494369d637bee5ff357de791b Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Tue, 24 Mar 2015 13:57:23 -0400 Subject: [PATCH] Implement hope_result:lift_map_exn/3 as base implementation of other lift_exn functions. --- src/hope_fun.erl | 10 ++++++ src/hope_result.erl | 64 ++++++++++++++++++++++++-------------- test/hope_fun_SUITE.erl | 41 ++++++++++++++++++++++++ test/hope_result_SUITE.erl | 28 +++++++++++++++++ 4 files changed, 120 insertions(+), 23 deletions(-) create mode 100644 src/hope_fun.erl create mode 100644 test/hope_fun_SUITE.erl diff --git a/src/hope_fun.erl b/src/hope_fun.erl new file mode 100644 index 0000000..61babe9 --- /dev/null +++ b/src/hope_fun.erl @@ -0,0 +1,10 @@ +-module(hope_fun). + +-export( + [ id/1 + ]). + +-spec id(A) -> + A. +id(X) -> + X. diff --git a/src/hope_result.erl b/src/hope_result.erl index dec6e79..930f2e0 100644 --- a/src/hope_result.erl +++ b/src/hope_result.erl @@ -4,6 +4,8 @@ -export_type( [ t/2 + , exn_class/0 + , exn_value/1 ]). -export( @@ -14,8 +16,17 @@ , pipe/2 , lift_exn/1 , lift_exn/2 + , lift_map_exn/3 ]). +-type exn_class() :: + error + | exit + | throw + . + +-type exn_value(A) :: + {exn_class(), A}. -type t(A, B) :: {ok, A} @@ -65,33 +76,40 @@ pipe([F|Fs], X) -> end. -spec lift_exn(F) -> G - when F :: fun((A)-> B) - , G :: fun((A)-> t(B, {Class, Reason :: any()})) - , Class :: error - | exit - | throw + when F :: fun((A) -> B) + , G :: fun((A) -> t(B, exn_value(any()))) . lift_exn(F) when is_function(F, 1) -> - fun(X) -> - try - {ok, F(X)} - catch Class:Reason -> - {error, {Class, Reason}} - end - end. + ID = fun hope_fun:id/1, + lift_map_exn(F, ID, ID). + +-spec lift_exn(F, ErrorTag) -> G + when F :: fun((A) -> B) + , G :: fun((A) -> t(B, {ErrorTag, exn_value(any())})) + . +lift_exn(F, ErrorTag) when is_function(F, 1) -> + ID = fun hope_fun:id/1, + Tag = fun (Reason) -> {ErrorTag, Reason} end, + lift_map_exn(F, ID, Tag). --spec lift_exn(F, Label) -> G - when F :: fun((A)-> B) - , G :: fun((A)-> t(B, {Label, {Class, Reason :: any()}})) - , Class :: error - | exit - | throw +-spec lift_map_exn(F, MapOk, MapError) -> G + when F :: fun((A) -> B) + , MapOk :: fun((B) -> C) + , MapError :: fun((exn_value(any())) -> Error) + , G :: fun((A) -> t(C, Error)) . -lift_exn(F, Label) when is_function(F, 1) -> +lift_map_exn(F, MapOk, MapError) when is_function(F, 1) -> fun(X) -> - try - {ok, F(X)} - catch Class:Reason -> - tag_error({error, {Class, Reason}}, Label) + Result = + try + {ok, F(X)} + catch Class:Reason -> + {error, {Class, Reason}} + end, + % Applying maps separately as to not unintentionally catch an exception + % raised in a map. + case Result + of {ok , _}=Ok -> map (Ok , MapOk) + ; {error, _}=Error -> map_error(Error, MapError) end end. diff --git a/test/hope_fun_SUITE.erl b/test/hope_fun_SUITE.erl new file mode 100644 index 0000000..d4e2281 --- /dev/null +++ b/test/hope_fun_SUITE.erl @@ -0,0 +1,41 @@ +-module(hope_fun_SUITE). + +%% Callbacks +-export( + [ all/0 + , groups/0 + ]). + +%% Test cases +-export( + [ t_id/1 + ]). + + +-define(GROUP, hope_fun). + + +%% ============================================================================ +%% Common Test callbacks +%% ============================================================================ + +all() -> + [ {group, ?GROUP} + ]. + +groups() -> + Tests = + [ t_id + ], + Properties = [parallel], + [ {?GROUP, Properties, Tests} + ]. + + +%% ============================================================================= +%% Test cases +%% ============================================================================= + +t_id(_Cfg) -> + X = foo, + X = hope_fun:id(X). diff --git a/test/hope_result_SUITE.erl b/test/hope_result_SUITE.erl index d323cee..ab31503 100644 --- a/test/hope_result_SUITE.erl +++ b/test/hope_result_SUITE.erl @@ -14,6 +14,7 @@ , t_pipe_error/1 , t_hope_result_specs/1 , t_lift_exn/1 + , t_lift_map_exn/1 , t_return/1 , t_map/1 , t_map_error/1 @@ -48,6 +49,7 @@ groups() -> ], LiftTests = [ t_lift_exn + , t_lift_map_exn ], OtherTests = [ t_return @@ -101,6 +103,32 @@ t_lift_exn(_Cfg) -> {error, {Class, Reason}} = G(ok), {error, {Label, {Class, Reason}}} = H(ok). +t_lift_map_exn(_Cfg) -> + FOk = fun ({}) -> foo end, + FExn = fun ({}) -> throw(baz) end, + MapOk = fun (foo) -> bar end, + MapOkThrows = fun (foo) -> throw(exn_from_ok_map) end, + MapError = fun ({throw, baz}) -> qux end, + MapErrorThrows = fun ({throw, baz}) -> throw(exn_from_error_map) end, + GOk = hope_result:lift_map_exn(FOk , MapOk , MapError), + GOkThrows = hope_result:lift_map_exn(FOk , MapOkThrows, MapError), + GError = hope_result:lift_map_exn(FExn, MapOk , MapError), + GErrorThrows = hope_result:lift_map_exn(FExn, MapOk , MapErrorThrows), + {ok, bar} = GOk({}), + {error, qux} = GError({}), + ok = + try + must_not_return = GOkThrows({}) + catch throw:exn_from_ok_map -> + ok + end, + ok = + try + must_not_return = GErrorThrows({}) + catch throw:exn_from_error_map -> + ok + end. + t_return(_Cfg) -> X = foo, {ok, X} = hope_result:return(X). -- 2.20.1