diff options
Diffstat (limited to 'docs/book/ERRORS.html')
-rw-r--r-- | docs/book/ERRORS.html | 116 |
1 files changed, 59 insertions, 57 deletions
diff --git a/docs/book/ERRORS.html b/docs/book/ERRORS.html index 1e4d27a..9ec3f6f 100644 --- a/docs/book/ERRORS.html +++ b/docs/book/ERRORS.html @@ -7,7 +7,7 @@ <!-- Custom HTML head --> - + <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="theme-color" content="#ffffff"> @@ -174,66 +174,84 @@ <div id="content" class="content"> <main> <h1 id="error-handling"><a class="header" href="#error-handling">Error Handling</a></h1> -<p>Puck's error handling is shamelessly stolen from Swift. It uses a combination of <code>Option</code>/<code>Result</code> types and <code>try</code>/<code>catch</code> statements, and leans somewhat on Puck's metaprogramming capabilities.</p> -<p>There are several ways to handle errors in Puck. If the error is encoded in the type, one can:</p> +<p>Puck's error handling is heavily inspired syntactically by Swift and semantically by the underlying effects system. It uses a combination of monadic error handling and effectful error propagation, with much in the way of syntactic sugar for conversion between the two, and leans somewhat heavily on Puck's metaprogramming capabilities. In comparison to Rust, it is considerably more dynamic by default.</p> +<p>There are several ways to handle errors in Puck. If the error is encoded in the type (as an <code>Option</code> or <code>Result</code> type), one can:</p> <ol> <li><code>match</code> on the error</li> <li>compactly match on the error with <code>if ... of</code></li> <li>propagate the error with <code>?</code></li> <li>throw the error with <code>!</code></li> </ol> -<p>If an error is thrown, one <strong>must</strong> explicitly handle (or disregard) it with a <code>try/catch</code> block or risk runtime failure. This method of error handling may feel more familiar to Java programmers.</p> -<h2 id="errors-as-monads"><a class="header" href="#errors-as-monads">Errors as Monads</a></h2> -<p>Puck provides <a href="std/default/options.pk"><code>Option[T]</code></a> and a <a href="std/default/results.pk"><code>Result[T, E]</code></a> types, imported by default. These are <code>union</code> types and so must be pattern matched upon to be useful: but the standard library provides <a href="std/default/results.pk">a bevy of helper functions</a>. +<p>If the error is thrown (encoded as an effect), one can:</p> +<ol> +<li>ignore the error, propagating it up the call stack</li> +<li>recover from the error in a <code>try</code> block</li> +<li>convert the error to a <code>Result[T]</code> (monadic form)</li> +</ol> +<p>If an error is thrown, one <em>must</em> explicitly handle it at some level of the stack, or risk runtime failure. This method of error handling may feel more familiar to Java programmers. The compiler will warn on - but not enforce catching - such unhandled errors.</p> +<h2 id="errors-as-monads"><a class="header" href="#errors-as-monads">Errors as monads</a></h2> +<p>Puck provides <a href="std/default/options.pk"><code>Option[T]</code></a> and a <a href="std/default/results.pk"><code>Result[T, E]</code></a> types, imported by default. These are <code>union</code> types under the hood and so must be pattern matched upon to be useful: but the standard library provides <a href="std/default/results.pk">a bevy of helper functions</a>. Two in particular are of note. The <code>?</code> operator unwraps a Result or propagates its error up a function call (and may only be used in type-appropriate contexts). The <code>!</code> operator unwraps an Option or Result directly or throws an exception in the case of None or Error.</p> -<pre><code class="language-puck">pub macro `?`[T, E](self: Result[T, E]) = - quote: +<pre><code class="language-puck">pub macro ?[T, E](self: Result[T, E]) = + quote match `self` - of Okay(x): x - of Error(e): return Error(e) + of Okay(x) then x + of Error(e) then return Error(e) </code></pre> -<pre><code class="language-puck">pub func `!`[T](self: Option[T]): T = +<pre><code class="language-puck">pub func ![T](self: Option[T]): T = match self - of Some(x): x - of None: raise EmptyValue + of Some(x) then x + of None then raise "empty value" -pub func `!`[T, E](self: Result[T, E]): T = - of Okay(x): x - of Error(e): raise e -</code></pre> -<p>The utility of the provided helpers in <a href="std/default/options.pk"><code>std.options</code></a> and <a href="std/default/results.pk"><code>std.results</code></a> should not be understated. While encoding errors into the type system may appear restrictive at first glance, some syntactic sugar goes a long way in writing compact and idiomatic code. Java programmers in particular are urged to give type-first errors a try, before falling back on unwraps and <code>try</code>/<code>catch</code>.</p> -<p>A notable helpful type is the aliasing of <code>Result[T]</code> to <code>Result[T, ref Err]</code>, for when the particular error does not matter. This breaks <code>try</code>/<code>catch</code> exhaustion (as <code>ref Err</code> denotes a reference to <em>any</em> Error), but is particularly useful when used in conjunction with the propagation operator.</p> -<h2 id="errors-as-catchable-exceptions"><a class="header" href="#errors-as-catchable-exceptions">Errors as Catchable Exceptions</a></h2> -<p>Errors raised by <code>raise</code>/<code>throw</code> (or subsequently the <code>!</code> operator) must be explicitly caught and handled via a <code>try</code>/<code>catch</code>/<code>finally</code> statement. -If an exception is not handled within a function body, the function must be explicitly marked as a throwing function via the <code>yeet</code> prefix (name to be determined). The compiler will statically determine which exceptions in particular are thrown from any given function, and enforce them to be explicitly handled or explicitly ignored.</p> -<p>Despite functioning here as exceptions: errors remain types. An error thrown from an unwrapped <code>Result[T, E]</code> is of type <code>E</code>. <code>catch</code> statements, then, may pattern match upon possible errors, behaving similarly to <code>of</code> branches.</p> -<pre><code class="language-puck">try: - ... -catch "Error": - ... -finally: - ... +pub func ![T, E](self: Result[T, E]): T = + match self + of Okay(x) then x + of Error(e) then raise e </code></pre> -<p>This creates a distinction between two types of error handling, working in sync: functional error handling with <a href="https://en.wikipedia.org/wiki/Option_type">Option</a> and <a href="https://en.wikipedia.org/wiki/Result_type">Result</a> types, and object-oriented error handling with <a href="https://en.wikipedia.org/wiki/Exception_handling">catchable exceptions</a>. These styles may be swapped between with minimal syntactic overhead. Libraries, however, should universally use <code>Option</code>/<code>Result</code>, as this provides the best support for both styles.</p> -<!-- [nullable types](https://en.wikipedia.org/wiki/Nullable_type)?? --> -<h2 id="errors-and-void-functions"><a class="header" href="#errors-and-void-functions">Errors and Void Functions</a></h2> -<p>Some functions do not return a value but can still fail: for example, setters. -This can make it difficult to do monadic error handling elegantly: one could return a <code>Result[void, E]</code>, but...</p> -<pre><code class="language-puck">pub func set[T](self: list[T], i: uint, val: T) = - if i > self.length: +<p>The utility of the provided helpers in <a href="std/default/options.pk"><code>std.options</code></a> and <a href="std/default/results.pk"><code>std.results</code></a> should not be understated. While encoding errors into the type system may appear restrictive at first glance, some syntactic sugar goes a long way in writing compact and idiomatic code. Java programmers in particular are urged to give type-first errors a try, before falling back on unwraps and <code>try</code>/<code>with</code>.</p> +<p>A notable helpful type is the aliasing of <code>Result[T]</code> to <code>Result[T, ref Err]</code>, for when the particular error does not matter. This breaks <code>match</code> exhaustion (as <code>ref Err</code> denotes a reference to <em>any</em> Error), but is particularly useful when used in conjunction with the propagation operator.</p> +<h2 id="errors-as-checked-exceptions"><a class="header" href="#errors-as-checked-exceptions">Errors as checked exceptions</a></h2> +<p>Some functions do not return a value but can still fail: for example, setters. This can make it difficult to do monadic error handling elegantly. One could return a <code>type Success[E] = Result[void, E]</code>, but such an approach is somewhat inelegant. Instead: we treat an <code>assert</code> within a function as having an <em>effect</em>: a possible failure, that can be handled and recovered from at any point in the call stack. If a possible exception is not handled within a function body, the function is implicitly marked by the compiler as throwing that exception.</p> +<pre><code class="language-puck">pub type list[T] = struct + data: ptr T + capacity: uint + length: uint + +@[safe] +pub func set[T](self: list[T], i: uint, val: T) = + if i > self.length then raise IndexOutOfBounds - self.data.raw_set(offset = i, val) + self.data.set(offset = i, val) + +var foo = ["Hello", "world"] +foo.set(0, "Goodbye") # set can panic +# this propagates an IndexOutOfBounds effect up the call stack. </code></pre> -<h2 id="unrecoverable-exceptions"><a class="header" href="#unrecoverable-exceptions">Unrecoverable Exceptions</a></h2> +<p>Despite functioning here as exceptions: errors remain types. An error thrown from an unwrapped <code>Result[T, E]</code> is of type <code>E</code>. <code>with</code> statements, then, may pattern match upon possible errors, behaving semantically and syntactically similarly to <code>of</code> branches: though notably not requiring exhaustion.</p> +<pre><code class="language-puck">try + foo.set(0, "Goodbye") +with IndexOutOfBounds(index) then + dbg "Index out of bounds at {}".fmt(index) + panic +finally + ... +</code></pre> +<p>This creates a distinction between two types of error handling, working in sync: functional error handling with <a href="https://en.wikipedia.org/wiki/Option_type">Option</a> and <a href="https://en.wikipedia.org/wiki/Result_type">Result</a> types, and <a href="https://en.wikipedia.org/wiki/Exception_handling">object-oriented error handling</a> with <a href="...">algebraic effects</a>. These styles may be swapped between with minimal syntactic overhead. It is up to libraries to determine which classes of errors are exceptional and best given the effect treatment and which should be explicitly handled monadically. Libraries should tend towards using <code>Option</code>/<code>Result</code> as this provides the best support for both styles (thanks to the <code>!</code> operator).</p> +<h2 id="unrecoverable-exceptions"><a class="header" href="#unrecoverable-exceptions">Unrecoverable exceptions</a></h2> <p>There exist errors from which a program can not reasonably recover. These are the following:</p> <ul> -<li><code>Assertation Failure</code>: a call to an <code>assert</code> function has returned false at runtime.</li> +<li><code>Assertation Failure</code>: a call to an unhandled <code>assert</code> function has returned false at runtime.</li> <li><code>Out of Memory</code>: the executable is out of memory.</li> <li><code>Stack Overflow</code>: the executable has overflowed the stack.</li> <li>any others?</li> </ul> -<p>They are not recoverable, but the user should be aware of them as possible failure conditions.</p> -<p>References: <a href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/errorhandling">Error Handling in Swift</a></p> +<p>They are not recoverable, and not handled within the effects system, but the user should be aware of them as possible failure conditions.</p> +<hr /> +<p>References</p> +<ul> +<li><a href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/errorhandling">Error Handling in Swift</a></li> +<li><a href="https://overreacted.io/algebraic-effects-for-the-rest-of-us/">Algebraic Effects for the rest of us</a></li> +</ul> </main> @@ -264,22 +282,6 @@ This can make it difficult to do monadic error handling elegantly: one could ret </div> - <!-- Livereload script (if served using the cli tool) --> - <script> - const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:'; - const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload"; - const socket = new WebSocket(wsAddress); - socket.onmessage = function (event) { - if (event.data === "reload") { - socket.close(); - location.reload(); - } - }; - - window.onbeforeunload = function() { - socket.close(); - } - </script> |