diff --git a/src/Core__Result.mjs b/src/Core__Result.mjs index 832ab968..6c49bd40 100644 --- a/src/Core__Result.mjs +++ b/src/Core__Result.mjs @@ -1,6 +1,7 @@ // Generated by ReScript, PLEASE EDIT WITH CARE import * as Curry from "rescript/lib/es6/curry.js"; +import * as Caml_option from "rescript/lib/es6/caml_option.js"; function getExn(x) { if (x.TAG === /* Ok */0) { @@ -102,6 +103,43 @@ function cmp(a, b, f) { } } +function fromArrayMap(xs, f) { + var oks = []; + var firstError; + var index = 0; + var $$break = false; + while(!$$break) { + var x = xs.at(index); + if (x !== undefined) { + var ok = Curry._1(f, Caml_option.valFromOption(x)); + if (ok.TAG === /* Ok */0) { + oks.push(ok._0); + index = index + 1 | 0; + } else { + firstError = ok; + $$break = true; + } + } else { + $$break = true; + } + }; + var err = firstError; + if (err !== undefined) { + return err; + } else { + return { + TAG: /* Ok */0, + _0: oks + }; + } +} + +function fromArray(xs) { + return fromArrayMap(xs, (function (i) { + return i; + })); +} + export { getExn , mapWithDefault , @@ -112,5 +150,7 @@ export { isError , eq , cmp , + fromArray , + fromArrayMap , } /* No side effect */ diff --git a/src/Core__Result.res b/src/Core__Result.res index 51032a64..e4e037bb 100644 --- a/src/Core__Result.res +++ b/src/Core__Result.res @@ -91,3 +91,33 @@ let cmpU = (a, b, f) => } let cmp = (a, b, f) => cmpU(a, b, (. x, y) => f(x, y)) + +// I don't think tail-call optimization is guaranteed +// so a non-recursive implementation is safer. +// https://chromestatus.com/feature/5516876633341952 +let fromArrayMap = (xs, f) => { + let oks = [] + let firstError = ref(None) + let index = ref(0) + let break = ref(false) + while !break.contents { + switch xs->Core__Array.at(index.contents) { + | None => break := true + | Some(x) => + switch f(x) { + | Ok(ok) => + oks->Core__Array.push(ok) + index := index.contents + 1 + | Error(_) as err => + firstError := Some(err) + break := true + } + } + } + switch firstError.contents { + | None => Ok(oks) + | Some(err) => err + } +} + +let fromArray = xs => xs->fromArrayMap(i => i) diff --git a/src/Core__Result.resi b/src/Core__Result.resi index d1e6636d..52120af7 100644 --- a/src/Core__Result.resi +++ b/src/Core__Result.resi @@ -197,3 +197,29 @@ let eq: (t<'a, 'c>, t<'b, 'd>, ('a, 'b) => bool) => bool ``` */ let cmp: (t<'a, 'c>, t<'b, 'd>, ('a, 'b) => int) => int + +/** +`fromArray(xs)` converts an array of results to a single result. If `xs` only contains `Ok` values, returns an `Ok` that contains an array of those values. Otherwise returns the first `Error`. + +## Examples + +```rescript +Result.fromArray([Ok(1), Ok(2)]) // Ok([1, 2]) +Result.fromArray([Ok(1), Error("a"), Ok(2)]) // Error("a") +Result.fromArray([]) // Ok([]) +``` +*/ +let fromArray: array> => result, 'b> + +/** +`fromArrayMap(xs, f)` lazily applies `f` to each item in `xs`. If there are only `Ok` results, returns an `Ok` that contains an array of those values. Otherwise returns the first `Error`. + +## Examples + +```rescript +let lessThanTen = n => n < 10 ? Ok(n) : Error(n) +Result.fromArray([6, 12, 18], lessThanTen) // Error(12) +Result.fromArray([3, 6, 9], lessThanTen) // Ok([3, 6, 9]) +``` +*/ +let fromArrayMap: (array<'v>, 'v => result<'a, 'b>) => result, 'b> diff --git a/test/ResultTests.mjs b/test/ResultTests.mjs new file mode 100644 index 00000000..229e753a --- /dev/null +++ b/test/ResultTests.mjs @@ -0,0 +1,226 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Test from "./Test.mjs"; +import * as Caml_obj from "rescript/lib/es6/caml_obj.js"; +import * as Core__Result from "../src/Core__Result.mjs"; + +var eq = Caml_obj.equal; + +var fromArrayTestCases = [ + [ + "when empty, return empty", + [], + { + TAG: /* Ok */0, + _0: [] + } + ], + [ + "when one error, return it", + [{ + TAG: /* Error */1, + _0: "a" + }], + { + TAG: /* Error */1, + _0: "a" + } + ], + [ + "when one ok, return it", + [{ + TAG: /* Ok */0, + _0: 1 + }], + { + TAG: /* Ok */0, + _0: [1] + } + ], + [ + "when all ok, return all", + [ + { + TAG: /* Ok */0, + _0: 1 + }, + { + TAG: /* Ok */0, + _0: 2 + }, + { + TAG: /* Ok */0, + _0: 3 + } + ], + { + TAG: /* Ok */0, + _0: [ + 1, + 2, + 3 + ] + } + ], + [ + "when all error, return first", + [ + { + TAG: /* Error */1, + _0: "a" + }, + { + TAG: /* Error */1, + _0: "b" + }, + { + TAG: /* Error */1, + _0: "c" + } + ], + { + TAG: /* Error */1, + _0: "a" + } + ], + [ + "when mix, return first error", + [ + { + TAG: /* Ok */0, + _0: 1 + }, + { + TAG: /* Error */1, + _0: "a" + }, + { + TAG: /* Ok */0, + _0: 2 + }, + { + TAG: /* Error */1, + _0: "b" + } + ], + { + TAG: /* Error */1, + _0: "a" + } + ] +]; + +fromArrayTestCases.forEach(function (param) { + Test.run([ + [ + "ResultTests.res", + 19, + 22, + 43 + ], + "fromArray: " + param[0] + "" + ], Core__Result.fromArray(param[1]), eq, param[2]); + }); + +var fromArrayMapTestCases = [ + [ + "when empty, return empty", + [], + { + TAG: /* Ok */0, + _0: [] + } + ], + [ + "when one error, return it", + [30], + { + TAG: /* Error */1, + _0: "30" + } + ], + [ + "when one ok, return it", + [2], + { + TAG: /* Ok */0, + _0: [4] + } + ], + [ + "when all ok, return all", + [ + 1, + 2, + 3 + ], + { + TAG: /* Ok */0, + _0: [ + 2, + 4, + 6 + ] + } + ], + [ + "when all error, return first", + [ + 20, + 30, + 40 + ], + { + TAG: /* Error */1, + _0: "20" + } + ], + [ + "when mix, return first error", + [ + 1, + 2, + 14, + 3, + 4 + ], + { + TAG: /* Error */1, + _0: "14" + } + ] +]; + +function fromArrayMap(n) { + if (n < 10) { + return { + TAG: /* Ok */0, + _0: (n << 1) + }; + } else { + return { + TAG: /* Error */1, + _0: n.toString() + }; + } +} + +fromArrayMapTestCases.forEach(function (param) { + Test.run([ + [ + "ResultTests.res", + 35, + 15, + 39 + ], + "fromArrayMap: " + param[0] + "" + ], Core__Result.fromArrayMap(param[1], fromArrayMap), eq, param[2]); + }); + +export { + eq , + fromArrayTestCases , + fromArrayMapTestCases , + fromArrayMap , +} +/* Not a pure module */ diff --git a/test/ResultTests.res b/test/ResultTests.res new file mode 100644 index 00000000..62a4e319 --- /dev/null +++ b/test/ResultTests.res @@ -0,0 +1,40 @@ +open RescriptCore + +let eq = (a, b) => a == b + +// ============ +// fromArrayMap +// ============ + +let fromArrayTestCases = [ + ("when empty, return empty", [], Ok([])), + ("when one error, return it", [Error("a")], Error("a")), + ("when one ok, return it", [Ok(1)], Ok([1])), + ("when all ok, return all", [Ok(1), Ok(2), Ok(3)], Ok([1, 2, 3])), + ("when all error, return first", [Error("a"), Error("b"), Error("c")], Error("a")), + ("when mix, return first error", [Ok(1), Error("a"), Ok(2), Error("b")], Error("a")), +] + +fromArrayTestCases->Array.forEach(((title, input, output)) => + Test.run(__POS_OF__(`fromArray: ${title}`), input->Result.fromArray, eq, output) +) + +let fromArrayMapTestCases = [ + ("when empty, return empty", [], Ok([])), + ("when one error, return it", [30], Error("30")), + ("when one ok, return it", [2], Ok([4])), + ("when all ok, return all", [1, 2, 3], Ok([2, 4, 6])), + ("when all error, return first", [20, 30, 40], Error("20")), + ("when mix, return first error", [1, 2, 14, 3, 4], Error("14")), +] + +let fromArrayMap = n => n < 10 ? Ok(n * 2) : Error(n->Int.toString) + +fromArrayMapTestCases->Array.forEach(((title, input, output)) => + Test.run( + __POS_OF__(`fromArrayMap: ${title}`), + input->Result.fromArrayMap(fromArrayMap), + eq, + output, + ) +) diff --git a/test/TestSuite.mjs b/test/TestSuite.mjs index c968bd8f..21f21a7b 100644 --- a/test/TestSuite.mjs +++ b/test/TestSuite.mjs @@ -5,6 +5,7 @@ import * as TestTests from "./TestTests.mjs"; import * as ArrayTests from "./ArrayTests.mjs"; import * as ErrorTests from "./ErrorTests.mjs"; import * as PromiseTest from "./PromiseTest.mjs"; +import * as ResultTests from "./ResultTests.mjs"; var bign = TestTests.bign; @@ -26,10 +27,16 @@ var Concurrently = PromiseTest.Concurrently; var panicTest = ErrorTests.panicTest; -var eq = IntTests.eq; - var $$catch = IntTests.$$catch; +var eq = ResultTests.eq; + +var fromArrayTestCases = ResultTests.fromArrayTestCases; + +var fromArrayMapTestCases = ResultTests.fromArrayMapTestCases; + +var fromArrayMap = ResultTests.fromArrayMap; + export { bign , TestError , @@ -41,7 +48,10 @@ export { Catching , Concurrently , panicTest , - eq , $$catch , + eq , + fromArrayTestCases , + fromArrayMapTestCases , + fromArrayMap , } /* IntTests Not a pure module */ diff --git a/test/TestSuite.res b/test/TestSuite.res index 6277bf57..56266ed3 100644 --- a/test/TestSuite.res +++ b/test/TestSuite.res @@ -3,3 +3,4 @@ include PromiseTest include ErrorTests include ArrayTests include IntTests +include ResultTests