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

effect.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 effect
#
#  Author: Fridtjof Siebert (siebert@tokiwa.software)
#
# -----------------------------------------------------------------------

# effect -- abstract parent feature for effects
#
# effect provides a means to perform effectful operations.  Instances
# of effect are installed in the current environment while their code is
# executed.  The code may access the effect via <type>.env.
#
module:public effect (
  # action to be performed, may include code to run while this effect is installed.
  #
  r effect_mode.val
  )
is

  match r
    _ effect_mode.plain   =>
    i effect_mode.inst    => run i.f ()->unit
    _ effect_mode.repl    => replace
    _ effect_mode.default => default
    _ effect_mode.new     =>

  public mode effect_mode.val =>
    match r
      effect_mode.plain => effect_mode.plain
      effect_mode.inst, effect_mode.repl, effect_mode.default, effect_mode.new => effect_mode.repl

  # replace effect in the current context by this
  module replace unit => intrinsic


  # execute code provided in f.call while this effect is installed
  # in the current environment. Return immediately in case abort is
  # called.
  #
  # NYI: uses type parameter T only to simplify C backend
  #
  private abortable(T type : Function unit, f T) unit => intrinsic


  # replace effect in the current context by this and abort current execution
  public abort void
  pre
    safety: abortable
  =>
    abort0

  # Intrinsic version of abort.
  #
  # NYI: precondition does not seem to work for an intrinsic yet, causes test failure e.g., in tests/local_mutate
  # so we use cannot make `abort` intrinsic.
  private abort0 void
  =>
    intrinsic


  # does this effect support abort?
  #
  # Redefining this to return `false` helps to detect unexptected calls to
  # `abort` at runtime and ensure that the static analysis finds that there
  # code executed with this effect will always return normally and produce
  # a result. This is used, e.g, in `mutate` to avoid static analysis
  # reporting `panic` as an effect of the use of a local mutate instance.
  #
  public abortable => true


  # set default effect in the current context to this if none is installed
  module default unit => intrinsic

  # execute the code of 'f' in the context of this effect
  #
  module run(R type, f () -> R, def ()->R) R =>
    cf := Effect_Call f
    abortable cf
    match cf.res
      nil => def()
      x R => x


  # abort the current execution and return from the surrounding call to
  # abortable with result == false.
  #
  public return void
  pre
    safety: abortable
  =>
    abort


  # has an effect of the given type been installed?
  public type.is_installed(E type) bool => intrinsic_constructor



# helper instance for effect.abortable to wrap call to f() into a ()->unit
#
private Effect_Call(R type, f () -> R) ref : Function unit is
  res option R := nil
  call =>
    set res := f()


# effect mode is an enum that determines how an instance of effect is used
#
public effect_mode is

  public plain is             # a plain effect, not installed as 'env'
  public repl is              # effect instance replaces previous one in 'env'
  public default is           # install as default. NYI: remove and use 'new' instead, see io.stdin.fz
  public new is               # a new effect to be installed
  public inst(f ()->unit) is  # install and run 'f'. NYI: remove and use 'new' instead, see io.stdin.fz

  public val : choice plain repl default new inst of


public code_effect : effect (effect_mode.inst code)
is

  # the code to be executed within this effect.  This is typically
  # redefined as an argument field of a sub-feature of effect.
  #
  # NYI: Currently, there is no direct way to return a result value
  # from the code.
  #
  public code ()->unit => abstract


# simple_effect provides a simple means to define and use an effect
#
# user-defined effects should inherit from this feature and add
# operations as inner features or fields of function type.
#
# To install this effect to execute a function, simple_effect.use
# can be called.
#
public simple_effect : effect effect_mode.plain
is

  # install this simple_effect and run code
  #
  # In case of an `abort`, `use` returns silently (NYI: maybe this should better
  # return an oucome with the abort wrapped in an error?).
  #
  public use(code ()->unit) unit =>
    cf := Effect_Call code
    abortable cf
    # ignore cf.res


  # install this effect and run code that produces a result of
  # type T. panic in case abort is called.
  #
  public go(T type, f ()->T) T =>
    cf := Effect_Call f
    abortable cf
    match cf.res
      nil => msg := "*** unexpected abort in {simple_effect.this.type}"
             if abortable
               panic msg
             else
               # the user should not be able to produce this state: since the effect is
               # not abortable cf can only return with a result. So we do a low-level
               # panic that does not show up as a used effect:
               fuzion.std.panic msg
      x T => x
last changed: 2024-03-07