X-Git-Url: https://git.xandkar.net/?a=blobdiff_plain;f=src%2Fhope_result.erl;h=9d4235be1001d1d388c03305bd16749d5a4937bf;hb=HEAD;hp=5d0ea7e42c40521a8a24f65b0fa0ce7f4701153c;hpb=ed9905af6fa1dba6f89759d6dbfc970426122bde;p=hope.git diff --git a/src/hope_result.erl b/src/hope_result.erl index 5d0ea7e..9d4235b 100644 --- a/src/hope_result.erl +++ b/src/hope_result.erl @@ -1,14 +1,36 @@ -module(hope_result). +-behavior(hope_gen_monad). -export_type( [ t/2 + , exn_class/0 + , exn_value/1 ]). -export( - [ pipe/2 + % Generic monad interface + [ return/1 + , map/2 % map/2 is alias for map_ok/2 + , pipe/2 + + % Specific to hope_result:t() + , map_ok/2 + , map_error/2 + , tag_error/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} @@ -16,6 +38,37 @@ . +-spec return(A) -> + {ok, A}. +return(X) -> + {ok, X}. + +-spec map(t(A, Error), fun((A) -> (B))) -> + t(B, Error). +map({_, _}=T, F) -> + map_ok(T, F). + +-spec map_ok(t(A, Error), fun((A) -> (B))) -> + t(B, Error). +map_ok({ok, X}, F) -> + {ok, F(X)}; +map_ok({error, _}=Error, _) -> + Error. + +-spec map_error(t(A, B), fun((B) -> (C))) -> + t(A, C). +map_error({ok, _}=Ok, _) -> + Ok; +map_error({error, Reason}, F) -> + {error, F(Reason)}. + +-spec tag_error(t(A, Reason), Tag) -> + t(A, {Tag, Reason}). +tag_error({ok, _}=Ok, _) -> + Ok; +tag_error({error, Reason}, Tag) -> + {error, {Tag, Reason}}. + -spec pipe([F], X) -> t(Ok, Error) when X :: any() @@ -23,9 +76,49 @@ , Error :: any() , F :: fun((X) -> t(Ok, Error)) . -pipe([] , X) -> X; +pipe([], X) -> + {ok, X}; pipe([F|Fs], X) -> case F(X) of {error, _}=E -> E ; {ok, Y} -> pipe(Fs, Y) end. + +-spec lift_exn(F) -> G + when F :: fun((A) -> B) + , G :: fun((A) -> t(B, exn_value(any()))) + . +lift_exn(F) when is_function(F, 1) -> + 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_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_map_exn(F, MapOk, MapError) when is_function(F, 1) -> + fun(X) -> + 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 (Ok , MapOk) + ; {error, _}=Error -> map_error(Error, MapError) + end + end.