From dc93c7a292fff6ac35051b4d31f4aa1b31f6708a Mon Sep 17 00:00:00 2001 From: Brendan Hansen Date: Sun, 26 Feb 2023 22:26:59 -0600 Subject: [PATCH] doc: result.onyx --- core/container/result.onyx | 77 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/core/container/result.onyx b/core/container/result.onyx index ddf9c85f..0e97d51a 100644 --- a/core/container/result.onyx +++ b/core/container/result.onyx @@ -1,13 +1,30 @@ package core +// +// Result is helper type that encapsulates the idea of a computation +// that could either succeed with a value, or fail with an error. +// Generally, this is only used as the return type of a procedure, +// but it can be used elsewhere. Like Optional, there are several +// helper methods that make it easier to work with Results. +// + + use core {Optional, conv} +#doc """ + Result(T, E) is a structure that represents either an Ok value + of type T, or an Err value of type E. `status` contains either + .Ok, or .Err depending on which is currently held. +""" @conv.Custom_Format.{ #solidify format {T=Ok_Type, E=Err_Type} } Result :: struct (Ok_Type: type_expr, Err_Type: type_expr) { status: Result_Status; __data: Result_Data(Ok_Type, Err_Type); } +#doc """ + The type of Result(T, E).status +""" Result_Status :: enum { Ok :: 1; Err :: 2; @@ -21,23 +38,34 @@ Result_Data :: struct (T: type_expr, E: type_expr) { #inject Result { + #doc "Quick way to return an Ok from a procedure." return_ok :: macro (x: $T) do return .{ .Ok, .{ value = x } }; + + #doc "Quick way to return an Err from a procedure." return_err :: macro (x: $T) do return .{ .Err, .{ error = x } }; + #doc "Returns true if the result contains an Ok value." is_ok :: (r: #Self) => r.status == .Ok; + #doc "Returns true if the result contains an Err value." is_err :: (r: #Self) => !r.status == .Err; + #doc "Returns an Optional of the Ok type." ok :: (r: #Self) -> Optional(r.Ok_Type) { if r.status == .Ok do return Optional.make(r.__data.value); return .{}; } + #doc "Returns an Optional of the Err type." err :: (r: #Self) -> Optional(r.Err_Type) { if r.status == .Err do return Optional.make(r.__data.error); return .{}; } + #doc """ + Forcefully extracts the Ok value out of the Result. If the + result contains an Err, an assertion is thrown. + """ unwrap :: (r: #Self) -> r.Ok_Type { if r.status == .Err { msg := tprintf("Unwrapping Result with error '{}'.", r.__data.error); @@ -48,12 +76,20 @@ Result_Data :: struct (T: type_expr, E: type_expr) { return r.__data.value; } + #doc """ + Tries to extract the Ok value out of the Result. If the + result contains an Err, the empty .{} value is returned. + """ unwrap_or_default :: (r: #Self) -> r.Ok_Type { if r.status == .Err do return .{}; return r.__data.value; } + #doc """ + Tries to extract the Ok value out of the Result. If the + result contains an Err, a custom assertion message is thrown. + """ expect :: (r: #Self, msg: str) -> r.Ok_Type { if r.status == .Err { assert(false, msg); @@ -63,21 +99,44 @@ Result_Data :: struct (T: type_expr, E: type_expr) { return r.__data.value; } - transform :: (r: #Self, f: (r.Ok_Type) -> $R) => { - if r.status == .Err do return r; + #doc """ + Returns a new result defined by: + Ok(n) => Ok(f(n)) + Err(e) => Err(e) + """ + transform :: (r: Result($T, $E), f: (T) -> $R) -> Result(R, E) { + if r.status == .Err do return .{ .Err, .{ error = r.error } }; return .{ .Ok, .{ value = f(r.__data.value) } }; } + #doc "Monadic chaining operation." and_then :: (r: #Self, f: (r.Ok_Type) -> Result($R, r.Err_Type)) -> Result(R, r.Err_Type) { if r.status == .Err do return r; return .{ .Ok, .{ value = f(r.__data.value) } }; } - or_else :: (r: Result, generate: () -> typeof r) => { + #doc "If the Result contains Err, generate is called to make a value" + or_else :: (r: #Self, generate: () -> typeof r) => { if r.status == .Ok do return r; return .{}; } + #doc """ + If result contains Err, the error is returned from the enclosing + procedure. Otherwise, the Ok value is returned. + + f :: () -> Result(i32, str) { + Result.return_err("Oh no..."); + } + + g :: () -> Result(str, str) { + // This returns from g with the error returned from f. + v := f()->forward_err(); + println(v); + + Result.return_ok("Success!"); + } + """ forward_err :: macro (r: Result($T, $E)) -> T { res := r; if res.status == .Ok do return res.__data.value; @@ -85,6 +144,10 @@ Result_Data :: struct (T: type_expr, E: type_expr) { return #from_enclosing .{ .Err, .{ error = res.__data.error } }; } + #doc """ + If result contains Err, the given value is returned from the + enclosing procedure. Otherwise, the Ok value is returned. + """ or_return :: macro (r: Result($T, $E), v: $V) -> T { res := r; if res.status == .Ok do return res.__data.value; @@ -92,6 +155,14 @@ Result_Data :: struct (T: type_expr, E: type_expr) { return #from_enclosing v; } + #doc """ + If result contains Err, the given code is run. This code is + expected to either: + - Return a good value with `return` + - Return an error value with `return #enclosing_scope` + + This procedure is subject to change. + """ catch :: macro (r: Result($T, $E), on_err: Code) -> T { res := r; if res.status == .Ok do return res.__data.value; -- 2.25.1