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 instated in the current environment while their code is
# executed.  The code may perform operations of the instated effect
# via <type>.env.  These operations may either return normally (resume) or
# abort the current computation, i.e., return directly to the call that
# instated the effect.
#
# Effects are identified by their type, including any actual type parameters.
# Effect instances must be assignable to the effect type.  This means that if
# the effect type is a value type, the instance must be an instance of exactly
# that type while if the effect type is a ref type, the instance might be of
# a type that inherits from the effect type that is --possibly after boxing--
# assignable to that type.
#
# Effect operations may replace an instated instance by a new value.
# This gives operations a means to become stateful.
#
# In case an effect performs an abort, the code the effect was instated for
# will be abandoned and the function passed via tha `def` argument to
# `instate` will be used to produce a result from the last effect instance.
#
# Effects may redefine the `finally` feature that is executed directly after
# an instated effect was removed, independent of the reason why it is removed.
# This means, finally is executed after code running in the effect returns
# normally, after a call to an operation that results in an abort or when
# a surrounding effect is aborted.
#
public effect is


  # --------------------  effect intrinsics  --------------------


  # get this effect from env
  #
  public type.from_env effect.this => intrinsic


  # set default effect in the current context to this if none is
  # installed
  #
  type.default0(e effect.this) unit => intrinsic


  # instate e for effect type effect.this in the current environment.
  #


  # execute code provided by `code` while this effect `e` is instated in the
  # current environment and remove the instated effect after `code` returned.
  #
  # In case of an abort, run `def` on the latest value instated or replaced
  # for `effect.this` after removing that instated effect value.
  #
  # Note the use of `_ : Function ...` instead of just `Function ...`: This
  # introduces type parameters.  This has several important implications:
  #
  # - First, it permits `code` and `def` to be of value types, and
  # - second, it simplifies the backends since calls to `code.call` or
  #   `def.call` are statically defined by that value type, no need for
  #   dynamic binding.
  #
  # As a neat side-effect this avoids heap allocation of the lambdas.
  #
  type.instate0(e effect.this,
                code _ : Function unit,
                def  _ : Function unit effect.this) unit => intrinsic


  # replace existing effect of type `E` in the new effect value `e`
  #
  type.replace0(e effect.this) unit => intrinsic


  # Intrinsic version of abort. For all surrounding instated effects,
  # remove the effect and run `finally` until we have reached an
  # effect of this type. Then return from that effect's `instate0` call
  # with the rsult produced by calling `def` on the currently instated
  # value.
  #
  type.abort0 void => intrinsic


  # has an effect of this type been instated?
  #
  type.is_instated0 bool => intrinsic


  # --------------------  default effects  --------------------


  # set default instance for effect type `effect.this` to `e`
  #
  # NYI: UNDER DEVELOPMENT: This is a manual work-around to automatically install
  # default effects. It should be replaced by a effect configuration file that
  # is defined locally to a fuzion project or globally to the fuzion installation
  # the defines the default effects to be used. The DFA should then automatically
  # determine the required effects and create code to  instate them at the beginning
  # of an application.
  #
  public type.default(e effect.this) => default0 e


  # convenience version of `type.default` for effect values whose type is
  # exactly the effect type (i.e., the value type does not inherit
  # from the effect type).
  #
  # set default instance for effect type `effect.this` to value `effect.this`.
  #
  # NYI: UNDER DEVELOPMENT: See type.default
  #
  public default unit
  =>
    effect.this.type.default effect.this


  # --------------------  effect instation  --------------------


  # execute 'code' in a context where the effect instance `e` has been
  # installed for effect type `effect.this`.
  #
  # In case `f` returns normally, this will return `f`'s result.
  #
  # In case `f` aborts this effect, return `def()`.
  #
  public type.instate(# result type
                      R type,

                      # the effect value to instate
                      e effect.this,

                      # the code to execute with `e` instated.
                      code () -> R,

                      # the lazy default result to use if effect aborts
                      def effect.this -> R
                      ) R
  =>
    x := instate_helper R effect.this e code def
    instate0 e x.call_code x.call_def
    x.res.get


  # execute 'code' in a context where the effect instance `e` has been
  # installed for effect type `effect.this`.
  #
  # In case `f` returns normally, this will return `f`'s result.
  #
  # In case `f` aborts this effect, this will panic.
  #
  public type.instate(# result type
                      R type,

                      # the effect value to instate
                      e effect.this,

                      # the code to execute with `e` instated.
                      code () -> R
                      ) R
  =>
    instate R e code (_ -> panic "unexpected abort in {effect.this.type}")


  # convenience version of `instate` for effect values whose type is
  # exactly the effect type (i.e., the value type does not inherit
  # from the effect type).
  #
  # Execute 'code' in a context where this effect instance has been
  # instated for effect type `effect.this`.
  #
  # In case `f` returns normally, this will return `f`'s result.
  #
  # In case `f` aborts this effect, this will panic.
  #
  public instate_self(# result type
                      R type,

                      # the code to execute with `e` instated.
                      code () -> R
                      ) R
  =>
    effect.this.instate R effect.this code



  # infix variant instate_self
  #
  # use it like this:
  #
  #     effect_instance ! ()->code
  #
  public infix !(# result type
                 R type,

                 # the code to execute with `e` instated.
                 code () -> R
  ) R
  =>
    instate_self code



  # infix variant of effect.instate
  #
  # use it like this:
  #
  #     effect_type <- effect_instance ! code
  #
  public type.infix <- (E type : effect, e E) =>
    effect0 e



  # has an effect of this type been instated?
  #
  public type.is_instated bool => is_instated0


  # has an effect of the given type been instated?
  public type.get_if_instated option effect.this
  =>
    if effect.this.is_instated
      unsafe_from_env
    else
      nil


  # internal helper to perform `E.env` without producing an error
  # in case static analysis fails to verify that `effect.this` is
  # actually instated.
  #
  type.unsafe_from_env effect.this => intrinsic


  # --------------------  replacing effect instances  --------------------


  # replace existing effect for type `effect.this` by the new effect value `e`.
  #
  # For effects that model the outside world (e.g., i/o, time, sensors and actuators, etc.),
  # the effect might be a unit type, so the replace is effectively a no-operation.  However,
  # the call to `replace` is used ot model the change of the outside world and must be
  # included for analysis tools to appreciate this.
  #
  # replace may only be called during the execution of an operation of a currently instated
  # effect of the same effect type.
  #
  # NYI: BUG: It is currently not enforced that replace is only called during the execution
  # of an operation of a currenlty instated effect of the same effect type.
  #
  public type.replace(e effect.this)
  =>
    replace0 e


  # replace existing effect of type `effect.this` by the new effect value `effect.this`.
  #
  # This is a convenience feature for value type effects for which the type of
  # the effect instance equals the effect type.  `ref` type effects typically have
  # values that may be children of the effect type that are of a different type, so
  # `effect_type.replace new_value` must be used.
  #
  # replace may only be called during the execution of an operation of a currently instated
  # effect of the same effect type.
  #
  public replace
  =>
    effect.this.type.replace effect.this


  # --------------------  aborting code execution  --------------------


  # replace existing effect for type `effect.this` by the new effect value `e`
  # and abort code execution to return to the point where the effect was instated.
  #
  public type.abort(e effect.this) void
  pre
    safety: effect.this.is_instated
    safety: effect.this.env.abortable
  =>
    replace0 e
    abort0


  # Abort code execution for the instated effect.this.env and return to the point
  # where the effect was instated.
  #
  public type.abort void
  pre
    safety: effect.this.is_instated
    safety: effect.this.env.abortable
  =>
    effect.this.abort0


  # 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 the
  # 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


  # abort the current execution and return from the surrounding call to
  # `instate`.
  #
  # NYI: CLEANUP: `return` is the same as `abort`. The term `return` seems
  # common for algebraic effects, but is confusing since it is different to
  # returning from a call. We need to decide to break with algebraic effect
  # jargon (and remove `return`) or to stick with it (and rename `abort` as
  # `return`).
  #
  public type.return void
  pre
    safety: effect.this.is_instated
    safety: effect.this.env.abortable
  =>
    abort


  # --------------------  resource cleanup  --------------------


  # feature that will be called on the final instance of this effect
  # after it has been de-instated.
  #
  # This happens either on a normal return form the code passed to
  # `instate`, on an abort perfomed on this effect or on an abort
  # performed on an effect instated prior to this effect's instation.
  #
  # This can be used to perform actions when leaving an effect such
  # a cleanup up resources that were created or opened by operations
  # of this effect.
  #
  public finally =>


  # internal feature that will be called on the instance of this effect
  # when it will be de-instated.
  #
  # This is just a trampoline for compiler convenience such that when
  # generating code for the instate0 intrinsic the compiler does
  # not need to care about dynamic binding if the current effect is a
  # `ref`.
  #
  static_finally => finally


# instante helper to hold the result of the effect
#
instate_helper(# result type
               R type,

               # effect type
               ET type: effect,

               # the effect value
               e ET,

               # the code to execute with `e` instated.
               code () -> R,

               # the lazy default result to use if effect aborts
               def ET -> R) is

  # we replace `code`/`def` by functions with unit type result
  # to reduce complexity of the `instate0` intrinsic.  We
  # collect the result in this field
  res := option R nil

  # wrapper to call `code` and store result in 'res`
  call_code : Function unit is
    redef call =>
      set res := code()

  # wrapper to call `def` on final effect value and store result in 'res`
  call_def : Function unit ET is
    redef call(cur_e ET) =>
      set res := def cur_e



# a constructor for holding an effect type and instance
#
# this allows defining operators
#
public effect0(E type : effect, e E) is


  # infix variant of effect.instate
  #
  # use it like this:
  #
  #     effect_type <- effect_instance ! code
  #
  public infix !(# result type
                 R type,

                 # the code to execute with `e` instated.
                 code () -> R
  ) R
  =>
    E.instate R e code
last changed: 2025-01-23