| 1 | -module(hope_result). |
| 2 | |
| 3 | -behavior(hope_gen_monad). |
| 4 | |
| 5 | -export_type( |
| 6 | [ t/2 |
| 7 | , exn_class/0 |
| 8 | , exn_value/1 |
| 9 | ]). |
| 10 | |
| 11 | -export( |
| 12 | % Generic monad interface |
| 13 | [ return/1 |
| 14 | , map/2 % map/2 is alias for map_ok/2 |
| 15 | , pipe/2 |
| 16 | |
| 17 | % Specific to hope_result:t() |
| 18 | , map_ok/2 |
| 19 | , map_error/2 |
| 20 | , tag_error/2 |
| 21 | , lift_exn/1 |
| 22 | , lift_exn/2 |
| 23 | , lift_map_exn/3 |
| 24 | ]). |
| 25 | |
| 26 | -type exn_class() :: |
| 27 | error |
| 28 | | exit |
| 29 | | throw |
| 30 | . |
| 31 | |
| 32 | -type exn_value(A) :: |
| 33 | {exn_class(), A}. |
| 34 | |
| 35 | -type t(A, B) :: |
| 36 | {ok, A} |
| 37 | | {error, B} |
| 38 | . |
| 39 | |
| 40 | |
| 41 | -spec return(A) -> |
| 42 | {ok, A}. |
| 43 | return(X) -> |
| 44 | {ok, X}. |
| 45 | |
| 46 | -spec map(t(A, Error), fun((A) -> (B))) -> |
| 47 | t(B, Error). |
| 48 | map({_, _}=T, F) -> |
| 49 | map_ok(T, F). |
| 50 | |
| 51 | -spec map_ok(t(A, Error), fun((A) -> (B))) -> |
| 52 | t(B, Error). |
| 53 | map_ok({ok, X}, F) -> |
| 54 | {ok, F(X)}; |
| 55 | map_ok({error, _}=Error, _) -> |
| 56 | Error. |
| 57 | |
| 58 | -spec map_error(t(A, B), fun((B) -> (C))) -> |
| 59 | t(A, C). |
| 60 | map_error({ok, _}=Ok, _) -> |
| 61 | Ok; |
| 62 | map_error({error, Reason}, F) -> |
| 63 | {error, F(Reason)}. |
| 64 | |
| 65 | -spec tag_error(t(A, Reason), Tag) -> |
| 66 | t(A, {Tag, Reason}). |
| 67 | tag_error({ok, _}=Ok, _) -> |
| 68 | Ok; |
| 69 | tag_error({error, Reason}, Tag) -> |
| 70 | {error, {Tag, Reason}}. |
| 71 | |
| 72 | -spec pipe([F], X) -> |
| 73 | t(Ok, Error) |
| 74 | when X :: any() |
| 75 | , Ok :: any() |
| 76 | , Error :: any() |
| 77 | , F :: fun((X) -> t(Ok, Error)) |
| 78 | . |
| 79 | pipe([], X) -> |
| 80 | {ok, X}; |
| 81 | pipe([F|Fs], X) -> |
| 82 | case F(X) |
| 83 | of {error, _}=E -> E |
| 84 | ; {ok, Y} -> pipe(Fs, Y) |
| 85 | end. |
| 86 | |
| 87 | -spec lift_exn(F) -> G |
| 88 | when F :: fun((A) -> B) |
| 89 | , G :: fun((A) -> t(B, exn_value(any()))) |
| 90 | . |
| 91 | lift_exn(F) when is_function(F, 1) -> |
| 92 | ID = fun hope_fun:id/1, |
| 93 | lift_map_exn(F, ID, ID). |
| 94 | |
| 95 | -spec lift_exn(F, ErrorTag) -> G |
| 96 | when F :: fun((A) -> B) |
| 97 | , G :: fun((A) -> t(B, {ErrorTag, exn_value(any())})) |
| 98 | . |
| 99 | lift_exn(F, ErrorTag) when is_function(F, 1) -> |
| 100 | ID = fun hope_fun:id/1, |
| 101 | Tag = fun (Reason) -> {ErrorTag, Reason} end, |
| 102 | lift_map_exn(F, ID, Tag). |
| 103 | |
| 104 | -spec lift_map_exn(F, MapOk, MapError) -> G |
| 105 | when F :: fun((A) -> B) |
| 106 | , MapOk :: fun((B) -> C) |
| 107 | , MapError :: fun((exn_value(any())) -> Error) |
| 108 | , G :: fun((A) -> t(C, Error)) |
| 109 | . |
| 110 | lift_map_exn(F, MapOk, MapError) when is_function(F, 1) -> |
| 111 | fun(X) -> |
| 112 | Result = |
| 113 | try |
| 114 | {ok, F(X)} |
| 115 | catch Class:Reason -> |
| 116 | {error, {Class, Reason}} |
| 117 | end, |
| 118 | % Applying maps separately as to not unintentionally catch an exception |
| 119 | % raised in a map. |
| 120 | case Result |
| 121 | of {ok , _}=Ok -> map_ok (Ok , MapOk) |
| 122 | ; {error, _}=Error -> map_error(Error, MapError) |
| 123 | end |
| 124 | end. |