outcome.fz
# This file is part of the Fuzion language implementation.
#
# The Fuzion language implementation is free software: you can redistribute it
# and/or modify it under the terms of the GNU General Public License as published
# by the Free Software Foundation, version 3 of the License.
#
# The Fuzion language implementation is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License along with The
# Fuzion language implementation. If not, see <https://www.gnu.org/licenses/>.
# -----------------------------------------------------------------------
#
# Tokiwa Software GmbH, Germany
#
# Source code of Fuzion standard library feature outcome
#
# Author: Fridtjof Siebert (siebert@tokiwa.software)
#
# -----------------------------------------------------------------------
# outcome -- result type for functions that may return an error
#
# outcome is a choice type that represents the result of a routine that
# may either produce something useful or fail producing an error condition.
#
# Several error conditions are needed if there are several very different
# reasons for an operation to fail, e.g.
#
# get_data (u User, t Type) outcome data IO_Error Permission_Error is
# if u.allowed_to_acces T
# (read_file t.file_name)?
# else
# Permission_Error u, t
#
# read_file t Type outcome data IO_Error is
# [..]
#
# Note that 'outcome data IO_Error' is not assignment compatible with
# 'outcome data IO_Error Permission_Error', it has to be unwrapped first.
# This unwrapping, however, requires very little boilerplate, it is done
# using the '?' on the result of the call to 'read_file': This unwraps
# 'outcome data IO_Error' into 'IO_Error', which would be returned abruptly,
# and 'data', which would be returned normally. Both are assignment
# compatible to 'outcome data IO_Error Permission_Error', so everything
# is fine.
#
public outcome(public T type) :
choice T error, /* ... NYI: this should be open generic! */
monad T (outcome T)
is
# Does this outcome contain a value of type T?
#
public ok => (outcome.this ? T => true
| error => false)
# short-hand postfix operator for 'ok'
#
public postfix ?? => ok
# Does this outcome contain an error
#
is_error => !ok
# short-hand postfix operator for 'is_error'
#
public postfix !! => is_error
# value of an outcome that is known to contain a value
#
# This can only be called in cases where it is known for sure that this
# outcomee is not an error. A runtime error will be created otherwise.
#
public val T
pre
safety: (outcome.this??)
=>
outcome.this ? v T => v
| error => panic "outcome.val called on error"
# value of an outcome or default if outcome contains err
#
public val(default T) T
=>
outcome.this ? v T => v
| error => default
# error of an outcome that is known to contain an error
#
# This can only be called in cases where it is known for sure that this
# outcome is an error. A runtime error will be created otherwise.
#
public err error
pre
safety: (outcome.this!!)
=>
outcome.this ? T => panic "outcome.err called on successful outcome"
| e error => e
# convert this outcome to an option
# if the outcome is an error, the option is empty
public as_option option T =>
match outcome.this
t T => t
error => nil
# converts outcome to a string
#
# returns the result of T.as_string for a successful outcome, or
# "--$e--" for e error.
#
public redef as_string =>
outcome.this ? v T => v.as_string
| e error => "--$e--"
# returns o if outcome is ok, otherwise return the outcome's own
# error.
public and (O type, o outcome O) outcome O =>
outcome.this ? _ T => o
| e error => e
# returns o if outcome is not ok, otherwise return this outcome's value.
public or (o outcome T) outcome T =>
if !ok
o
else
outcome.this
# monadic operator
#
public redef infix >>= (f T -> outcome T) => outcome.this.bind f
# monadic operator
#
# This enables a very idiomatic way of error handling: assume you are trying
# to read 42 bytes from a file into a string. Let open be a feature that takes
# a file name and returns an outcome of some abstract file descriptor, let read
# be a feature that takes an abstract file descriptor and a number of bytes to
# read and returns an outcome of a byte (u8) array. We want to read 42 bytes
# from a file called "example.txt" into a string, and we wrap this into an outcome,
# for handling the case that an error occurs when opening the file (i.e. it does
# not exist) or when reading the file (i.e. example.txt is not actually a file but
# a directory). Using match expressions, this is very cumbersome:
#
# # open example file
# match open "example.txt"
# fd i64 =>
# # read 42 bytes from example file
# match read fd 42
# content array u8 => strings.from_bytes content
# re error => re
# oe error => oe
#
# The monadic operator turns this into:
#
# (open "example.txt").bind (array u8) (fd->read fd 42)
#
public bind(B type, f T -> outcome B) outcome B =>
outcome.this ? v T => f v
| e error => e
# return function
#
public fixed type.return (a T) => outcome a
# outcome with 1 argument provides an short-hand to wrap a value into a
# outcome
#
# Using this enables to write
#
# o := outcome x
#
# instead of
#
# o outcome TypeOfX := x
#
public outcome(T type, o outcome T) => o
last changed: 2024-03-07