Fuzion Logo
fuzion-lang.dev — The Fuzion Language Portal
JavaScript seems to be disabled. Functionality is limited.

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