From 5855df992a8776393ad06e7191115836eed92249 Mon Sep 17 00:00:00 2001 From: jmagaram Date: Mon, 13 Mar 2023 13:35:11 -0700 Subject: [PATCH 1/3] Result.fromArray --- src/Core__Result.mjs | 29 ++++++ src/Core__Result.res | 23 +++++ src/Core__Result.resi | 25 +++++ test/ResultTests.mjs | 226 ++++++++++++++++++++++++++++++++++++++++++ test/ResultTests.res | 40 ++++++++ test/TestSuite.mjs | 16 ++- test/TestSuite.res | 1 + 7 files changed, 357 insertions(+), 3 deletions(-) create mode 100644 test/ResultTests.mjs create mode 100644 test/ResultTests.res diff --git a/src/Core__Result.mjs b/src/Core__Result.mjs index 832ab968..62e9549d 100644 --- a/src/Core__Result.mjs +++ b/src/Core__Result.mjs @@ -102,6 +102,33 @@ function cmp(a, b, f) { } } +function fromArrayWith(xs, f) { + var oks = new Array(xs.length); + var _i = 0; + while(true) { + var i = _i; + if (i >= xs.length) { + return { + TAG: /* Ok */0, + _0: oks + }; + } + var x = Curry._1(f, xs[i]); + if (x.TAG !== /* Ok */0) { + return x; + } + oks[i] = x._0; + _i = i + 1 | 0; + continue ; + }; +} + +function fromArray(xs) { + return fromArrayWith(xs, (function (i) { + return i; + })); +} + export { getExn , mapWithDefault , @@ -112,5 +139,7 @@ export { isError , eq , cmp , + fromArray , + fromArrayWith , } /* No side effect */ diff --git a/src/Core__Result.res b/src/Core__Result.res index 51032a64..f45ba3aa 100644 --- a/src/Core__Result.res +++ b/src/Core__Result.res @@ -91,3 +91,26 @@ let cmpU = (a, b, f) => } let cmp = (a, b, f) => cmpU(a, b, (. x, y) => f(x, y)) + +// Written in imperative style for performance. The source +// array is scanned only until an error is found. +@new external makeUninitializedUnsafe: int => array<'a> = "Array" +let fromArrayWith = (xs, f) => { + let setUnsafe = Core__Array.setUnsafe + let getUnsafe = Core__Array.getUnsafe + let oks = makeUninitializedUnsafe(xs->Array.length) + let rec loop = i => + if i >= xs->Array.length { + Ok(oks) + } else { + switch xs->getUnsafe(i)->f { + | Ok(x) => + oks->setUnsafe(i, x) + loop(i + 1) + | Error(_) as err => err + } + } + loop(0) +} + +let fromArray = xs => xs->fromArrayWith(i => i) diff --git a/src/Core__Result.resi b/src/Core__Result.resi index d1e6636d..038f97bb 100644 --- a/src/Core__Result.resi +++ b/src/Core__Result.resi @@ -197,3 +197,28 @@ 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)`: If `xs` exclusively contains `Ok` values, returns an +`Ok` array of those values. Otherwise returns the first `Error`. + +```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> + +/** +`fromArrayWith(xs, f)`: Lazily applies `f` to each item in the source. +If there are only `Ok` results, returns an `Ok` array of those values. +Otherwise returns the first `Error`. + +```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 fromArrayWith: (array<'v>, 'v => result<'a, 'b>) => result, 'b> diff --git a/test/ResultTests.mjs b/test/ResultTests.mjs new file mode 100644 index 00000000..ea0cd506 --- /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 fromArrayWithTestCases = [ + [ + "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 fromArrayWithMapper(n) { + if (n < 10) { + return { + TAG: /* Ok */0, + _0: (n << 1) + }; + } else { + return { + TAG: /* Error */1, + _0: n.toString() + }; + } +} + +fromArrayWithTestCases.forEach(function (param) { + Test.run([ + [ + "ResultTests.res", + 35, + 15, + 40 + ], + "fromArrayWith: " + param[0] + "" + ], Core__Result.fromArrayWith(param[1], fromArrayWithMapper), eq, param[2]); + }); + +export { + eq , + fromArrayTestCases , + fromArrayWithTestCases , + fromArrayWithMapper , +} +/* Not a pure module */ diff --git a/test/ResultTests.res b/test/ResultTests.res new file mode 100644 index 00000000..aa61aa9b --- /dev/null +++ b/test/ResultTests.res @@ -0,0 +1,40 @@ +open RescriptCore + +let eq = (a, b) => a == b + +// ========= +// fromArray +// ========= + +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 fromArrayWithTestCases = [ + ("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 fromArrayWithMapper = n => n < 10 ? Ok(n * 2) : Error(n->Int.toString) + +fromArrayWithTestCases->Array.forEach(((title, input, output)) => + Test.run( + __POS_OF__(`fromArrayWith: ${title}`), + input->Result.fromArrayWith(fromArrayWithMapper), + eq, + output, + ) +) diff --git a/test/TestSuite.mjs b/test/TestSuite.mjs index c968bd8f..d17057a5 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 fromArrayWithTestCases = ResultTests.fromArrayWithTestCases; + +var fromArrayWithMapper = ResultTests.fromArrayWithMapper; + export { bign , TestError , @@ -41,7 +48,10 @@ export { Catching , Concurrently , panicTest , - eq , $$catch , + eq , + fromArrayTestCases , + fromArrayWithTestCases , + fromArrayWithMapper , } /* 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 From 6cbe1a44eb8444a00cdbb6537317112e7f1d4d81 Mon Sep 17 00:00:00 2001 From: jmagaram Date: Mon, 13 Mar 2023 14:10:04 -0700 Subject: [PATCH 2/3] Result.fromArrayMap cleaned up a bit --- src/Core__Result.mjs | 49 ++++++++++++++++++++++++++----------------- src/Core__Result.res | 43 +++++++++++++++++++++---------------- src/Core__Result.resi | 11 +++++----- test/ResultTests.mjs | 16 +++++++------- test/ResultTests.res | 16 +++++++------- test/TestSuite.mjs | 8 +++---- 6 files changed, 81 insertions(+), 62 deletions(-) diff --git a/src/Core__Result.mjs b/src/Core__Result.mjs index 62e9549d..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,29 +103,39 @@ function cmp(a, b, f) { } } -function fromArrayWith(xs, f) { - var oks = new Array(xs.length); - var _i = 0; - while(true) { - var i = _i; - if (i >= xs.length) { - return { - TAG: /* Ok */0, - _0: oks - }; - } - var x = Curry._1(f, xs[i]); - if (x.TAG !== /* Ok */0) { - return x; +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; } - oks[i] = x._0; - _i = i + 1 | 0; - continue ; }; + var err = firstError; + if (err !== undefined) { + return err; + } else { + return { + TAG: /* Ok */0, + _0: oks + }; + } } function fromArray(xs) { - return fromArrayWith(xs, (function (i) { + return fromArrayMap(xs, (function (i) { return i; })); } @@ -140,6 +151,6 @@ export { eq , cmp , fromArray , - fromArrayWith , + fromArrayMap , } /* No side effect */ diff --git a/src/Core__Result.res b/src/Core__Result.res index f45ba3aa..e4e037bb 100644 --- a/src/Core__Result.res +++ b/src/Core__Result.res @@ -92,25 +92,32 @@ let cmpU = (a, b, f) => let cmp = (a, b, f) => cmpU(a, b, (. x, y) => f(x, y)) -// Written in imperative style for performance. The source -// array is scanned only until an error is found. -@new external makeUninitializedUnsafe: int => array<'a> = "Array" -let fromArrayWith = (xs, f) => { - let setUnsafe = Core__Array.setUnsafe - let getUnsafe = Core__Array.getUnsafe - let oks = makeUninitializedUnsafe(xs->Array.length) - let rec loop = i => - if i >= xs->Array.length { - Ok(oks) - } else { - switch xs->getUnsafe(i)->f { - | Ok(x) => - oks->setUnsafe(i, x) - loop(i + 1) - | Error(_) as err => err +// 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 } } - loop(0) + } + switch firstError.contents { + | None => Ok(oks) + | Some(err) => err + } } -let fromArray = xs => xs->fromArrayWith(i => i) +let fromArray = xs => xs->fromArrayMap(i => i) diff --git a/src/Core__Result.resi b/src/Core__Result.resi index 038f97bb..11ae3f53 100644 --- a/src/Core__Result.resi +++ b/src/Core__Result.resi @@ -200,7 +200,8 @@ let cmp: (t<'a, 'c>, t<'b, 'd>, ('a, 'b) => int) => int /** `fromArray(xs)`: If `xs` exclusively contains `Ok` values, returns an -`Ok` array of those values. Otherwise returns the first `Error`. +`Ok` array of those values. Otherwise returns the first `Error` encountered +while scanning from beginning to end. ```rescript Result.fromArray([Ok(1), Ok(2)]) == Ok([1, 2]) @@ -211,9 +212,9 @@ Result.fromArray([]) == Ok([]) let fromArray: array> => result, 'b> /** -`fromArrayWith(xs, f)`: Lazily applies `f` to each item in the source. -If there are only `Ok` results, returns an `Ok` array of those values. -Otherwise returns the first `Error`. +`fromArrayMap(xs, f)`: Lazily applies `f` to each item in the source from beginning +to end. If there are only `Ok` results, returns an `Ok` array of those values. +Otherwise returns the first `Error` encountered. ```rescript let lessThanTen = n => n < 10 ? Ok(n) : Error(n) @@ -221,4 +222,4 @@ Result.fromArray([6, 12, 18], lessThanTen) == Error(12) Result.fromArray([3, 6, 9], lessThanTen) == Ok([3, 6, 9]) ``` */ -let fromArrayWith: (array<'v>, 'v => result<'a, 'b>) => result, 'b> +let fromArrayMap: (array<'v>, 'v => result<'a, 'b>) => result, 'b> diff --git a/test/ResultTests.mjs b/test/ResultTests.mjs index ea0cd506..229e753a 100644 --- a/test/ResultTests.mjs +++ b/test/ResultTests.mjs @@ -122,7 +122,7 @@ fromArrayTestCases.forEach(function (param) { ], Core__Result.fromArray(param[1]), eq, param[2]); }); -var fromArrayWithTestCases = [ +var fromArrayMapTestCases = [ [ "when empty, return empty", [], @@ -191,7 +191,7 @@ var fromArrayWithTestCases = [ ] ]; -function fromArrayWithMapper(n) { +function fromArrayMap(n) { if (n < 10) { return { TAG: /* Ok */0, @@ -205,22 +205,22 @@ function fromArrayWithMapper(n) { } } -fromArrayWithTestCases.forEach(function (param) { +fromArrayMapTestCases.forEach(function (param) { Test.run([ [ "ResultTests.res", 35, 15, - 40 + 39 ], - "fromArrayWith: " + param[0] + "" - ], Core__Result.fromArrayWith(param[1], fromArrayWithMapper), eq, param[2]); + "fromArrayMap: " + param[0] + "" + ], Core__Result.fromArrayMap(param[1], fromArrayMap), eq, param[2]); }); export { eq , fromArrayTestCases , - fromArrayWithTestCases , - fromArrayWithMapper , + fromArrayMapTestCases , + fromArrayMap , } /* Not a pure module */ diff --git a/test/ResultTests.res b/test/ResultTests.res index aa61aa9b..62a4e319 100644 --- a/test/ResultTests.res +++ b/test/ResultTests.res @@ -2,9 +2,9 @@ open RescriptCore let eq = (a, b) => a == b -// ========= -// fromArray -// ========= +// ============ +// fromArrayMap +// ============ let fromArrayTestCases = [ ("when empty, return empty", [], Ok([])), @@ -19,7 +19,7 @@ fromArrayTestCases->Array.forEach(((title, input, output)) => Test.run(__POS_OF__(`fromArray: ${title}`), input->Result.fromArray, eq, output) ) -let fromArrayWithTestCases = [ +let fromArrayMapTestCases = [ ("when empty, return empty", [], Ok([])), ("when one error, return it", [30], Error("30")), ("when one ok, return it", [2], Ok([4])), @@ -28,12 +28,12 @@ let fromArrayWithTestCases = [ ("when mix, return first error", [1, 2, 14, 3, 4], Error("14")), ] -let fromArrayWithMapper = n => n < 10 ? Ok(n * 2) : Error(n->Int.toString) +let fromArrayMap = n => n < 10 ? Ok(n * 2) : Error(n->Int.toString) -fromArrayWithTestCases->Array.forEach(((title, input, output)) => +fromArrayMapTestCases->Array.forEach(((title, input, output)) => Test.run( - __POS_OF__(`fromArrayWith: ${title}`), - input->Result.fromArrayWith(fromArrayWithMapper), + __POS_OF__(`fromArrayMap: ${title}`), + input->Result.fromArrayMap(fromArrayMap), eq, output, ) diff --git a/test/TestSuite.mjs b/test/TestSuite.mjs index d17057a5..21f21a7b 100644 --- a/test/TestSuite.mjs +++ b/test/TestSuite.mjs @@ -33,9 +33,9 @@ var eq = ResultTests.eq; var fromArrayTestCases = ResultTests.fromArrayTestCases; -var fromArrayWithTestCases = ResultTests.fromArrayWithTestCases; +var fromArrayMapTestCases = ResultTests.fromArrayMapTestCases; -var fromArrayWithMapper = ResultTests.fromArrayWithMapper; +var fromArrayMap = ResultTests.fromArrayMap; export { bign , @@ -51,7 +51,7 @@ export { $$catch , eq , fromArrayTestCases , - fromArrayWithTestCases , - fromArrayWithMapper , + fromArrayMapTestCases , + fromArrayMap , } /* IntTests Not a pure module */ From 58770829b0d981d1da8997675bad4a5d3d95856e Mon Sep 17 00:00:00 2001 From: jmagaram Date: Tue, 14 Mar 2023 10:27:27 -0700 Subject: [PATCH 3/3] clean up docs, use Example header, shorter text --- src/Core__Result.resi | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Core__Result.resi b/src/Core__Result.resi index 11ae3f53..52120af7 100644 --- a/src/Core__Result.resi +++ b/src/Core__Result.resi @@ -199,27 +199,27 @@ 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)`: If `xs` exclusively contains `Ok` values, returns an -`Ok` array of those values. Otherwise returns the first `Error` encountered -while scanning from beginning to end. +`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([]) +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 the source from beginning -to end. If there are only `Ok` results, returns an `Ok` array of those values. -Otherwise returns the first `Error` encountered. +`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]) +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>