doc: result.onyx
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Mon, 27 Feb 2023 04:26:59 +0000 (22:26 -0600)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Mon, 27 Feb 2023 04:26:59 +0000 (22:26 -0600)
core/container/result.onyx

index ddf9c85f04e3d0aec7ab2538aac36c3fa90dcd95..0e97d51a1f47cd58617090991ddd16e2727618c1 100644 (file)
@@ -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;