From 7bce7cf800092e01897f585d39b1eded1a25b60a Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Fri, 19 Sep 2025 14:34:46 +0200 Subject: [PATCH 1/3] Stdlib: Add mapOkAsync, mapErrorAsync, flatMapOkAsync and flatMapErrorAsync to result --- packages/@rescript/runtime/Stdlib_Result.res | 24 ++++ packages/@rescript/runtime/Stdlib_Result.resi | 118 +++++++++++++++--- .../runtime/lib/es6/Stdlib_Result.js | 58 +++++++++ .../@rescript/runtime/lib/js/Stdlib_Result.js | 58 +++++++++ 4 files changed, 242 insertions(+), 16 deletions(-) diff --git a/packages/@rescript/runtime/Stdlib_Result.res b/packages/@rescript/runtime/Stdlib_Result.res index 47654d231a..bb6c4eea67 100644 --- a/packages/@rescript/runtime/Stdlib_Result.res +++ b/packages/@rescript/runtime/Stdlib_Result.res @@ -174,3 +174,27 @@ let all6 = ((a, b, c, d, e, f)) => { } external ignore: result<'res, 'err> => unit = "%ignore" + +let mapOkAsync = async (res, f) => + switch await res { + | Ok(value) => Ok(f(value)) + | Error(err) => Error(err) + } + +let mapErrorAsync = async (res, f) => + switch await res { + | Ok(value) => Ok(value) + | Error(err) => Error(f(err)) + } + +let flatMapOkAsync = async (res, f) => + switch await res { + | Ok(value) => await f(value) + | Error(err) => Error(err) + } + +let flatMapErrorAsync = async (res, f) => + switch await res { + | Ok(value) => Ok(value) + | Error(err) => await f(err) + } diff --git a/packages/@rescript/runtime/Stdlib_Result.resi b/packages/@rescript/runtime/Stdlib_Result.resi index 3096cf72be..e676de49cf 100644 --- a/packages/@rescript/runtime/Stdlib_Result.resi +++ b/packages/@rescript/runtime/Stdlib_Result.resi @@ -54,14 +54,14 @@ type t<'res, 'err> = result<'res, 'err> = Ok('res) | Error('err) Result.getExn(Result.Ok(42)) == 42 switch Result.getExn(Error("Invalid data")) { - | exception _ => assert(true) - | _ => assert(false) - } + | exception _ => true + | _ => false + } == true switch Result.getExn(Error("Invalid data"), ~message="was Error!") { - | exception _ => assert(true) // Throws a JsError with the message "was Error!" - | _ => assert(false) - } + | exception _ => true // Throws a JsError with the message "was Error!" + | _ => false + } == true ``` */ @deprecated("Use 'getOrThrow' instead") @@ -74,14 +74,14 @@ let getExn: (result<'a, 'b>, ~message: string=?) => 'a Result.getOrThrow(Result.Ok(42)) == 42 switch Result.getOrThrow(Error("Invalid data")) { - | exception _ => assert(true) - | _ => assert(false) - } + | exception _ => true + | _ => false + } == true switch Result.getOrThrow(Error("Invalid data"), ~message="was Error!") { - | exception _ => assert(true) // Throws a JsError with the message "was Error!" - | _ => assert(false) - } + | exception _ => true // Throws a JsError with the message "was Error!" + | _ => false + } == true ``` */ let getOrThrow: (result<'a, 'b>, ~message: string=?) => 'a @@ -259,8 +259,8 @@ let forEach: (result<'a, 'b>, 'a => unit) => unit ```rescript let format = n => `Error code: ${n->Int.toString}` -Result.mapError(Error(14), format) // Error("Error code: 14") -Result.mapError(Ok("abc"), format) // Ok("abc") +Result.mapError(Error(14), format) == Error("Error code: 14") +Result.mapError(Ok("abc"), format) == Ok("abc") ``` */ let mapError: (result<'a, 'b>, 'b => 'c) => result<'a, 'c> @@ -269,8 +269,8 @@ let mapError: (result<'a, 'b>, 'b => 'c) => result<'a, 'c> `all(results)` returns a result of array if all options are Ok, otherwise returns Error. ## Examples ```rescript -Result.all([Ok(1), Ok(2), Ok(3)]) // Ok([1, 2, 3]) -Result.all([Ok(1), Error(1)]) // Error(1) +Result.all([Ok(1), Ok(2), Ok(3)]) == Ok([1, 2, 3]) +Result.all([Ok(1), Error(1)]) == Error(1) ``` */ let all: array> => result, 'b> @@ -321,3 +321,89 @@ let all6: ( without having to store or process it further. */ external ignore: result<'res, 'err> => unit = "%ignore" + +/** +`mapOkAsync(res, f)`: Asynchronously maps over the Ok value of a Result. When `res` is `Ok(n)`, +applies the async function `f` to `n` and wraps the result in `Ok`. +When `res` is `Error`, returns the error unchanged. + +## Examples + +```rescript +let asyncSquare = async x => x * x + +let result1 = Result.mapOkAsync(Promise.resolve(Ok(4)), asyncSquare) // Returns promise that resolves to Ok(16) +let result2 = Result.mapOkAsync(Promise.resolve(Error("invalid")), asyncSquare) // Returns promise that resolves to Error("invalid") +``` +*/ +let mapOkAsync: ( + promise>, + 'ok => 'mappedOk, +) => promise> + +/** +`mapErrorAsync(res, f)`: Asynchronously maps over the Error value of a Result. When `res` is `Error(e)`, +applies the async function `f` to `e` and wraps the result in `Error`. +When `res` is `Ok`, returns the ok value unchanged. + +## Examples + +```rescript +let asyncFormatError = async e => `Error: ${e}` + +let result1 = Result.mapErrorAsync(Promise.resolve(Ok(42)), asyncFormatError) // Returns promise that resolves to Ok(42) +let result2 = Result.mapErrorAsync(Promise.resolve(Error("invalid")), asyncFormatError) // Returns promise that resolves to Error("Error: invalid") +``` +*/ +let mapErrorAsync: ( + promise>, + 'error => 'mappedError, +) => promise> + +/** +`flatMapOkAsync(res, f)`: Asynchronously flat-maps over the Ok value of a Result. When `res` is `Ok(n)`, +applies the async function `f` to `n` which returns a Promise of a Result. +When `res` is `Error`, returns the error unchanged. + +## Examples + +```rescript +let asyncValidate = async x => + if x > 0 { + Ok(x * 2) + } else { + Error("Must be positive") + } + +let result1 = Result.flatMapOkAsync(Promise.resolve(Ok(5)), asyncValidate) // Returns promise that resolves to Ok(10) +let result2 = Result.flatMapOkAsync(Promise.resolve(Error("Already failed")), asyncValidate) // Returns promise that resolves to Error("Already failed") +``` +*/ +let flatMapOkAsync: ( + promise>, + 'ok => promise>, +) => promise> + +/** +`flatMapErrorAsync(res, f)`: Asynchronously flat-maps over the Error value of a Result. When `res` is `Error(e)`, +applies the async function `f` to `e` which returns a Promise of a Result. +When `res` is `Ok`, returns the ok value unchanged. + +## Examples + +```rescript +let asyncRecover = async error => + if error === "timeout" { + Ok("default") + } else { + Error(error) + } + +let result1 = Result.flatMapErrorAsync(Promise.resolve(Error("timeout")), asyncRecover) // Returns promise that resolves to Ok("default") +let result2 = Result.flatMapErrorAsync(Promise.resolve(Ok("default")), asyncRecover) // Returns promise that resolves to Ok("default") +``` +*/ +let flatMapErrorAsync: ( + promise>, + 'error => promise>, +) => promise> diff --git a/packages/@rescript/runtime/lib/es6/Stdlib_Result.js b/packages/@rescript/runtime/lib/es6/Stdlib_Result.js index 191540fa71..f3f9f0e5cb 100644 --- a/packages/@rescript/runtime/lib/es6/Stdlib_Result.js +++ b/packages/@rescript/runtime/lib/es6/Stdlib_Result.js @@ -341,6 +341,60 @@ function all6(param) { } } +async function mapOkAsync(res, f) { + let value = await res; + if (value.TAG === "Ok") { + return { + TAG: "Ok", + _0: f(value._0) + }; + } else { + return { + TAG: "Error", + _0: value._0 + }; + } +} + +async function mapErrorAsync(res, f) { + let value = await res; + if (value.TAG === "Ok") { + return { + TAG: "Ok", + _0: value._0 + }; + } else { + return { + TAG: "Error", + _0: f(value._0) + }; + } +} + +async function flatMapOkAsync(res, f) { + let value = await res; + if (value.TAG === "Ok") { + return await f(value._0); + } else { + return { + TAG: "Error", + _0: value._0 + }; + } +} + +async function flatMapErrorAsync(res, f) { + let value = await res; + if (value.TAG === "Ok") { + return { + TAG: "Ok", + _0: value._0 + }; + } else { + return await f(value._0); + } +} + let getExn = getOrThrow; let mapWithDefault = mapOr; @@ -368,5 +422,9 @@ export { all4, all5, all6, + mapOkAsync, + mapErrorAsync, + flatMapOkAsync, + flatMapErrorAsync, } /* No side effect */ diff --git a/packages/@rescript/runtime/lib/js/Stdlib_Result.js b/packages/@rescript/runtime/lib/js/Stdlib_Result.js index 0205dd16fb..a4c2b148c2 100644 --- a/packages/@rescript/runtime/lib/js/Stdlib_Result.js +++ b/packages/@rescript/runtime/lib/js/Stdlib_Result.js @@ -341,6 +341,60 @@ function all6(param) { } } +async function mapOkAsync(res, f) { + let value = await res; + if (value.TAG === "Ok") { + return { + TAG: "Ok", + _0: f(value._0) + }; + } else { + return { + TAG: "Error", + _0: value._0 + }; + } +} + +async function mapErrorAsync(res, f) { + let value = await res; + if (value.TAG === "Ok") { + return { + TAG: "Ok", + _0: value._0 + }; + } else { + return { + TAG: "Error", + _0: f(value._0) + }; + } +} + +async function flatMapOkAsync(res, f) { + let value = await res; + if (value.TAG === "Ok") { + return await f(value._0); + } else { + return { + TAG: "Error", + _0: value._0 + }; + } +} + +async function flatMapErrorAsync(res, f) { + let value = await res; + if (value.TAG === "Ok") { + return { + TAG: "Ok", + _0: value._0 + }; + } else { + return await f(value._0); + } +} + let getExn = getOrThrow; let mapWithDefault = mapOr; @@ -367,4 +421,8 @@ exports.all3 = all3; exports.all4 = all4; exports.all5 = all5; exports.all6 = all6; +exports.mapOkAsync = mapOkAsync; +exports.mapErrorAsync = mapErrorAsync; +exports.flatMapOkAsync = flatMapOkAsync; +exports.flatMapErrorAsync = flatMapErrorAsync; /* No side effect */ From 4247f40f01cfff5480deba65e2331c1e85c3a416 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Fri, 19 Sep 2025 14:42:49 +0200 Subject: [PATCH 2/3] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa2bfa774c..d88da3c00e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ #### :rocket: New Feature - Add `littleEndian` feature for `DataView` to Stdlib. https://github.com/rescript-lang/rescript/pull/7881 +- Add `mapOkAsync`, `mapErrorAsync`, `flatMapOkAsync` and `flatMapErrorAsync` for async `result`'s to Stdlib. https://github.com/rescript-lang/rescript/pull/7906 #### :bug: Bug fix From 72fa0fa0197afc5dd11e19336d4801372f3da92d Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Fri, 19 Sep 2025 14:58:56 +0200 Subject: [PATCH 3/3] Update completions --- tests/analysis_tests/tests/src/expected/Completion.res.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/analysis_tests/tests/src/expected/Completion.res.txt b/tests/analysis_tests/tests/src/expected/Completion.res.txt index 0e525703b2..2912c7a590 100644 --- a/tests/analysis_tests/tests/src/expected/Completion.res.txt +++ b/tests/analysis_tests/tests/src/expected/Completion.res.txt @@ -2613,13 +2613,13 @@ Path g "kind": 12, "tags": [1], "detail": "(result<'a, 'b>, ~message: string=?) => 'a", - "documentation": {"kind": "markdown", "value": "Deprecated: Use 'getOrThrow' instead\n\n\n `getExn(res, ~message=?)` returns `n` if `res` is `Ok(n)`, otherwise throws an exception with the message provided, or a generic message if no message was provided.\n\n ```res example\n Result.getExn(Result.Ok(42)) == 42\n \n switch Result.getExn(Error(\"Invalid data\")) {\n | exception _ => assert(true)\n | _ => assert(false)\n }\n\n switch Result.getExn(Error(\"Invalid data\"), ~message=\"was Error!\") {\n | exception _ => assert(true) // Throws a JsError with the message \"was Error!\"\n | _ => assert(false)\n }\n ```\n"} + "documentation": {"kind": "markdown", "value": "Deprecated: Use 'getOrThrow' instead\n\n\n `getExn(res, ~message=?)` returns `n` if `res` is `Ok(n)`, otherwise throws an exception with the message provided, or a generic message if no message was provided.\n\n ```res example\n Result.getExn(Result.Ok(42)) == 42\n \n switch Result.getExn(Error(\"Invalid data\")) {\n | exception _ => true\n | _ => false\n } == true\n\n switch Result.getExn(Error(\"Invalid data\"), ~message=\"was Error!\") {\n | exception _ => true // Throws a JsError with the message \"was Error!\"\n | _ => false\n } == true\n ```\n"} }, { "label": "Result.getOrThrow", "kind": 12, "tags": [], "detail": "(result<'a, 'b>, ~message: string=?) => 'a", - "documentation": {"kind": "markdown", "value": "\n `getOrThrow(res, ~message=?)` returns `n` if `res` is `Ok(n)`, otherwise throws an exception with the message provided, or a generic message if no message was provided.\n\n ```res example\n Result.getOrThrow(Result.Ok(42)) == 42\n \n switch Result.getOrThrow(Error(\"Invalid data\")) {\n | exception _ => assert(true)\n | _ => assert(false)\n }\n\n switch Result.getOrThrow(Error(\"Invalid data\"), ~message=\"was Error!\") {\n | exception _ => assert(true) // Throws a JsError with the message \"was Error!\"\n | _ => assert(false)\n }\n ```\n"} + "documentation": {"kind": "markdown", "value": "\n `getOrThrow(res, ~message=?)` returns `n` if `res` is `Ok(n)`, otherwise throws an exception with the message provided, or a generic message if no message was provided.\n\n ```res example\n Result.getOrThrow(Result.Ok(42)) == 42\n \n switch Result.getOrThrow(Error(\"Invalid data\")) {\n | exception _ => true\n | _ => false\n } == true\n\n switch Result.getOrThrow(Error(\"Invalid data\"), ~message=\"was Error!\") {\n | exception _ => true // Throws a JsError with the message \"was Error!\"\n | _ => false\n } == true\n ```\n"} }, { "label": "Result.getOr", "kind": 12,