diff options
-rw-r--r-- | gen/any.erl | 62 | ||||
-rw-r--r-- | gen/result.erl | 17 | ||||
-rw-r--r-- | src/any.gleam | 287 | ||||
-rw-r--r-- | src/gleam__stdlib.erl | 39 | ||||
-rw-r--r-- | src/result.gleam | 21 |
5 files changed, 403 insertions, 23 deletions
diff --git a/gen/any.erl b/gen/any.erl index bfca689..a29ed3d 100644 --- a/gen/any.erl +++ b/gen/any.erl @@ -1,10 +1,70 @@ -module(any). -compile(no_auto_import). +-include_lib("eunit/include/eunit.hrl"). --export([from/1, unsafeCoerce/1]). +-export([from/1, unsafeCoerce/1, string/1, int/1, float/1, bool/1, thunk/1, tuple/1]). from(A) -> gleam__stdlib:identity(A). unsafeCoerce(A) -> gleam__stdlib:identity(A). + +string(A) -> + gleam__stdlib:decode_string(A). + +-ifdef(TEST). +string_test() -> + expect:equal(string(from(<<"">>)), {ok, <<"">>}), + expect:equal(string(from(<<"Hello">>)), {ok, <<"Hello">>}), + expect:equal(string(from(1)), {error, <<"Expected a String, got `1`">>}), + expect:equal(string(from([])), {error, <<"Expected a String, got `[]`">>}). +-endif. + +int(A) -> + gleam__stdlib:decode_int(A). + +-ifdef(TEST). +int_test() -> + expect:equal(int(from(1)), {ok, 1}), + expect:equal(int(from(2)), {ok, 2}), + expect:equal(int(from(1.0)), {error, <<"Expected an Int, got `1.0`">>}), + expect:equal(int(from([])), {error, <<"Expected an Int, got `[]`">>}). +-endif. + +float(A) -> + gleam__stdlib:decode_float(A). + +-ifdef(TEST). +float_test() -> + expect:equal(float(from(1.0)), {ok, 1.0}), + expect:equal(float(from(2.2)), {ok, 2.2}), + expect:equal(float(from(1)), {error, <<"Expected a Float, got `1`">>}), + expect:equal(float(from([])), {error, <<"Expected a Float, got `[]`">>}). +-endif. + +bool(A) -> + gleam__stdlib:decode_bool(A). + +-ifdef(TEST). +bool_test() -> + expect:equal(bool(from(true)), {ok, true}), + expect:equal(bool(from(false)), {ok, false}), + expect:equal(bool(from(1)), {error, <<"Expected a Bool, got `1`">>}), + expect:equal(bool(from([])), {error, <<"Expected a Bool, got `[]`">>}). +-endif. + +thunk(A) -> + gleam__stdlib:thunk(A). + +tuple(A) -> + gleam__stdlib:decode_tuple(A). + +-ifdef(TEST). +tuple_test() -> + expect:equal(tuple(from({1, []})), {ok, {from(1), from([])}}), + expect:equal(tuple(from({<<"ok">>, <<"ok">>})), + {ok, {from(<<"ok">>), from(<<"ok">>)}}), + expect:is_error(tuple(from({1}))), + expect:is_error(tuple(from({1, 2, 3}))). +-endif. diff --git a/gen/result.erl b/gen/result.erl index b52cadd..19d87ed 100644 --- a/gen/result.erl +++ b/gen/result.erl @@ -2,7 +2,7 @@ -compile(no_auto_import). -include_lib("eunit/include/eunit.hrl"). --export([is_ok/1, is_error/1, map/2, map_error/2, flatten/1, flat_map/2, unwrap/2]). +-export([is_ok/1, is_error/1, map/2, map_error/2, flatten/1, then/2, unwrap/2]). is_ok(Result) -> case Result of @@ -39,13 +39,14 @@ map(Result, Fun) -> {ok, X} -> {ok, Fun(X)}; - {error, _} -> - Result + {error, E} -> + {error, E} end. -ifdef(TEST). map_test() -> expect:equal(map({ok, 1}, fun(X) -> X + 1 end), {ok, 2}), + expect:equal(map({ok, 1}, fun(_) -> <<"2">> end), {ok, <<"2">>}), expect:equal(map({error, 1}, fun(X) -> X + 1 end), {error, 1}). -endif. @@ -81,7 +82,7 @@ flatten_test() -> expect:equal(flatten({error, {error, 1}}), {error, {error, 1}}). -endif. -flat_map(Result, Fun) -> +then(Result, Fun) -> case Result of {ok, X} -> case Fun(X) of @@ -97,10 +98,10 @@ flat_map(Result, Fun) -> end. -ifdef(TEST). -flat_map_test() -> - expect:equal(flat_map({error, 1}, fun(X) -> {ok, X + 1} end), {error, 1}), - expect:equal(flat_map({ok, 1}, fun(X) -> {ok, X + 1} end), {ok, 2}), - expect:equal(flat_map({ok, 1}, fun(_) -> {error, 1} end), {error, 1}). +then_test() -> + expect:equal(then({error, 1}, fun(X) -> {ok, X + 1} end), {error, 1}), + expect:equal(then({ok, 1}, fun(X) -> {ok, X + 1} end), {ok, 2}), + expect:equal(then({ok, 1}, fun(_) -> {error, 1} end), {error, 1}). -endif. unwrap(Result, Default) -> diff --git a/src/any.gleam b/src/any.gleam index 884a436..41abed3 100644 --- a/src/any.gleam +++ b/src/any.gleam @@ -1,3 +1,7 @@ +import list +import result +import expect + // `Any` data is data that we don"t know the type of yet. // We likely get data like this from interop with Erlang, or from // IO with the outside world. @@ -14,3 +18,286 @@ pub external fn from(a) -> Any = "gleam__stdlib" "identity"; // native Erlang APIs. It is to be used as a last measure only. // pub external fn unsafeCoerce(a) -> b = "gleam__stdlib" "identity"; + +pub external fn string(Any) -> Result(String, String) + = "gleam__stdlib" "decode_string" + +test string { + "" + |> from + |> string + |> expect:equal(_, Ok("")) + + "Hello" + |> from + |> string + |> expect:equal(_, Ok("Hello")) + + 1 + |> from + |> string + |> expect:equal(_, Error("Expected a String, got `1`")) + + [] + |> from + |> string + |> expect:equal(_, Error("Expected a String, got `[]`")) +} + +pub external fn int(Any) -> Result(Int, String) + = "gleam__stdlib" "decode_int" + +test int { + 1 + |> from + |> int + |> expect:equal(_, Ok(1)) + + 2 + |> from + |> int + |> expect:equal(_, Ok(2)) + + 1.0 + |> from + |> int + |> expect:equal(_, Error("Expected an Int, got `1.0`")) + + [] + |> from + |> int + |> expect:equal(_, Error("Expected an Int, got `[]`")) +} + +pub external fn float(Any) -> Result(Float, String) + = "gleam__stdlib" "decode_float" + +test float { + 1.0 + |> from + |> float + |> expect:equal(_, Ok(1.0)) + + 2.2 + |> from + |> float + |> expect:equal(_, Ok(2.2)) + + 1 + |> from + |> float + |> expect:equal(_, Error("Expected a Float, got `1`")) + + [] + |> from + |> float + |> expect:equal(_, Error("Expected a Float, got `[]`")) +} + +// pub external fn atom(Any) -> Result(Atom, String) +// = "gleam__stdlib" "decode_atom" + +//// test atom { +//// // TODO +//// // make an atom here +//// // |> from +//// // |> atom +//// // |> expect:equal(_, Ok("")) + +//// // TODO +//// // make an atom here +//// // |> from +//// // |> atom +//// // |> expect:equal(_, Ok("ok")) + +//// let _ = 1 +//// |> from +//// |> atom +//// |> expect:is_error + +//// [] +//// |> from +//// |> atom +//// |> expect:is_error +//// } + +pub external fn bool(Any) -> Result(Bool, String) + = "gleam__stdlib" "decode_bool" + +test bool { + True + |> from + |> bool + |> expect:equal(_, Ok(True)) + + False + |> from + |> bool + |> expect:equal(_, Ok(False)) + + 1 + |> from + |> bool + |> expect:equal(_, Error("Expected a Bool, got `1`")) + + [] + |> from + |> bool + |> expect:equal(_, Error("Expected a Bool, got `[]`")) +} + +pub external fn thunk(Any) -> Result(fn() -> Any, String) + = "gleam__stdlib" "thunk" + +//// test thunk { +//// let _ = fn() { 1 } +//// |> from +//// |> thunk +//// |> expect:is_ok + +//// let _ = fn(x) { x } +//// |> from +//// |> thunk +//// |> expect:is_error + +//// let _ = 1 +//// |> from +//// |> thunk +//// |> expect:is_error + +//// [] +//// |> from +//// |> thunk +//// |> expect:is_error +//// } + +// external fn list_any(Any) -> Result(List(Any), String) = "gleam__stdlib" "decode_list" + +// fn list_module() { +// list +// } + +// pub fn list(any, decode) { +// any +// |> list_any +// |> result:then(_, fn(x) { list_module():traverse(x, decode) }) +// } + +//// test list { +//// let _ = [] +//// |> from +//// |> list(string) +//// |> expect:equal(_, Ok([])) + +//// let _ = [] +//// |> from +//// |> list(atom) +//// |> expect:equal(_, Ok([])) + +//// let _ = [1, 2, 3] +//// |> from +//// |> list(int) +//// |> expect:equal(_, Ok([1, 2, 3])) + +//// let _ = [[1], [2], [3]] +//// |> from +//// |> list(list(int)) +//// |> expect:equal(_, Ok([1, 2, 3])) + +//// let _ = 1 +//// |> from +//// |> list(string) +//// |> expect:is_error + +//// let _ = 1.0 +//// |> from() +//// |> list(int) +//// |> expect:is_error + +//// let _ = [""] +//// |> from() +//// |> list(int) +//// |> expect:is_error + +//// [from(1), any:from("not an int")] +//// |> from +//// |> list(int) +//// |> expect:is_error +//// } + +pub external fn tuple(Any) -> Result({Any, Any}, String) + = "gleam__stdlib" "decode_tuple" + +test tuple { + {1, []} + |> from + |> tuple + |> expect:equal(_, Ok({from(1), from([])})) + + {"ok", "ok"} + |> from + |> tuple + |> expect:equal(_, Ok({from("ok"), from("ok")})) + + {1} + |> from + |> tuple + |> expect:is_error + + {1, 2, 3} + |> from + |> tuple + |> expect:is_error + +// {1, 2.0} +// |> from +// |> tuple +// |> result:map(_, fn(x) { +// let {a, b} = x +// a |> int |> result:map(_, fn(x) { {x, b} }) +// }) +// // |> result:then(_, fn(x) { +// // let {a, b} = x +// // b |> float |> result:map(_, fn(x) { {a, x} }) +// // }) +// // |> expect:equal(_, Ok({1, 2.0})) +} + +////// TODO: FIXME: This doesn't work anymore because atoms are no longer a thing. +////// "Decode a field from a map, extracting a specified field. +////// +////// Multiple fields can be extracted and stored in a new record like so: +////// +////// Ok(name) <- decode:field(raw_data, \"name\") |> result:then(_, decode:string) +////// Ok(size) <- decode:field(raw_data, \"size\") |> result:then(_, decode:int) +////// Ok({ name = name, size = size }) +////// +////pub external fn field(Any, a) -> Result(String, Any) +//// = "gleam__stdlib" "field" + +////test field { +//// let _ = {ok = 1} +//// |> from +//// |> field("ok") +//// |> expect:equal(from(1)) + +//// let _ = {earlier = 2, ok = 3} +//// |> from +//// |> field("ok") +//// |> expect:equal(from(3)) + +//// let _ = {} +//// |> from +//// |> field("ok") +//// |> expect:is_error + +//// let _ = 1 +//// |> from +//// |> field("ok") +//// |> expect:is_error + +//// [] +//// |> from +//// |> field("ok") +//// |> expect:is_error +////} diff --git a/src/gleam__stdlib.erl b/src/gleam__stdlib.erl index 24668a7..f377e3f 100644 --- a/src/gleam__stdlib.erl +++ b/src/gleam__stdlib.erl @@ -1,9 +1,12 @@ -module(gleam__stdlib). -include_lib("eunit/include/eunit.hrl"). --export([expect_equal/2, expect_not_equal/2, expect_true/1, expect_false/1, expect_is_ok/1, - expect_is_error/1, atom_from_string/1, atom_create_from_string/1, atom_to_string/1, - map_fetch/2, iodata_append/2, iodata_prepend/2, identity/1]). +-export([expect_equal/2, expect_not_equal/2, expect_true/1, expect_false/1, + expect_is_ok/1, expect_is_error/1, atom_from_string/1, + atom_create_from_string/1, atom_to_string/1, map_fetch/2, + iodata_append/2, iodata_prepend/2, identity/1, decode_int/1, + decode_string/1, decode_bool/1, decode_float/1, decode_thunk/1, + decode_tuple/1, decode_field/2]). expect_equal(A, Expected) -> ?assertEqual(Expected, A). expect_not_equal(A, Expected) -> ?assertNotEqual(Expected, A). @@ -33,3 +36,33 @@ iodata_append(Iodata, String) -> [Iodata, String]. iodata_prepend(Iodata, String) -> [String, Iodata]. identity(X) -> X. + +decode_error_msg(Type, Data) -> + {error, iolist_to_binary(io_lib:format("Expected ~s, got `~p`", [Type, Data]))}. + +decode_string(Data) when is_binary(Data) -> {ok, Data}; +decode_string(Data) -> decode_error_msg("a String", Data). + +decode_int(Data) when is_integer(Data) -> {ok, Data}; +decode_int(Data) -> decode_error_msg("an Int", Data). + +decode_float(Data) when is_float(Data) -> {ok, Data}; +decode_float(Data) -> decode_error_msg("a Float", Data). + +decode_bool(Data) when is_boolean(Data) -> {ok, Data}; +decode_bool(Data) -> decode_error_msg("a Bool", Data). + +decode_thunk(Data) when is_function(Data, 0) -> {ok, Data}; +decode_thunk(Data) -> decode_error_msg("a zero arity function", Data). + +decode_tuple(Data = {_, _}) -> {ok, Data}; +decode_tuple(Data) -> decode_error_msg("a 2 element tuple", Data). + +decode_field(Data, Key) -> + case Data of + #{Key := Value} -> + {ok, Value}; + + _ -> + decode_error_msg(io_lib:format("a map with key `~p`", [Key]), Data) + end. diff --git a/src/result.gleam b/src/result.gleam index 113925e..18503ec 100644 --- a/src/result.gleam +++ b/src/result.gleam @@ -2,11 +2,6 @@ import expect // Result represents the result of something that may succeed or fail. // `Ok` means it was successful, `Error` means it failed. -// -pub enum Result(error, value) = - | Ok(value) - | Error(error) -; pub fn is_ok(result) { case result { @@ -38,7 +33,7 @@ test is_error { pub fn map(result, fun) { case result { | Ok(x) -> Ok(fun(x)) - | Error(_) -> result + | Error(e) -> Error(e) } } @@ -47,6 +42,10 @@ test map { |> map(_, fn(x) { x + 1 }) |> expect:equal(_, Ok(2)) + Ok(1) + |> map(_, fn(_) { "2" }) + |> expect:equal(_, Ok("2")) + Error(1) |> map(_, fn(x) { x + 1 }) |> expect:equal(_, Error(1)) @@ -90,7 +89,7 @@ test flatten { |> expect:equal(_, Error(Error(1))) } -pub fn flat_map(result, fun) { +pub fn then(result, fun) { case result { | Ok(x) -> case fun(x) { @@ -101,17 +100,17 @@ pub fn flat_map(result, fun) { } } -test flat_map { +test then { Error(1) - |> flat_map(_, fn(x) { Ok(x + 1) }) + |> then(_, fn(x) { Ok(x + 1) }) |> expect:equal(_, Error(1)) Ok(1) - |> flat_map(_, fn(x) { Ok(x + 1) }) + |> then(_, fn(x) { Ok(x + 1) }) |> expect:equal(_, Ok(2)) Ok(1) - |> flat_map(_, fn(_) { Error(1) }) + |> then(_, fn(_) { Error(1) }) |> expect:equal(_, Error(1)) } |