3.4. kink/CONTROL

The mod which provides various control funs.

3.4.1. CONTROL.ensure($body $cleanup)

CONTROL.ensure.ensure ensures $cleanup thunk is invoked when the invocation of $body thunk terminates.

CONTROL.ensure invokes $body with no args.

If the invocation of $body terminates without raising an exception, escaping by CONTROL.escape, or calling CORE.shift to an outer continuation tag, $cleanup is invoked in an execution stack different from the current execution stack. If the execution stack terminates without raising an exception, CONTROL.ensure returns the result of the invocation of $body. If the execution stack terminates raising an exception, CONTROL.ensure reraises the exception.

If the invocation of $body terminates raising an exception, or escaping by CONTROL.escape, $cleanup is called by CORE.reraise or CONTROL.escape in an execution stack different from the current execution stack.

Example:

:CONTROL.require_from('kink/')

:divide_10_by <- {(:Divisor)
  :do_division <- {
    CONTROL.ensure(
      { 10 // Divisor }
      { stdout.print_line('exit from $do_division') }
    )
  }
  CONTROL.ensure(
    { do_division }
    { stdout.print_line('exit from $divide_10_by') }
  )
}

stdout.print_line(divide_10_by(2).repr)
# Output:
#   exit from $do_division
#   exit from $divide_10_by
#   5

CONTROL.try(
  { divide_10_by(0) }
  { raise('must not reach here') }
  {(:Msg :Traces)
    stdout.print_line('exception: {}'.format(Msg))
  }
)
# Output:
#   exit from $do_division
#   exit from $divide_10_by
#   exception: Num.op_intdiv: zero division: 10 is divided by 0

3.4.2. CONTROL.escape(Kont_tag Result)

CONTROL.escape does non-local return to the continuation tag specified by Kont_tag, with the result specified as Result.

Preconditions:

• Kont_tag must be a str

• There must be a delimitor tag with Kont_tag as the continuation tag

On escaping, cleanup thunks registered by CONTROL.ensure are invoked from inner ones to outer ones.

This fun is intended to be used by funs which aborts the control and does not expect resume operations. Examples are:

• kink/CORE.reraise

• kink/CONTROL.with_break

• Java_val.throw

Example:

:CONTROL.require_from('kink/')

:Result <- reset('tag'){
  CONTROL.ensure(
    { CONTROL.escape('tag' 42) }
    { stdout.print_line('bye') }
  )

  stdout.print_line('must not reach here')
}
# => bye

stdout.print_line(Result.repr)  # => 42

3.4.3. CONTROL.try($body $on_returned $on_raised)

CONTROL.try traps an exception raised by an invocation of $body.

Preconditions:

• $body must be a thunk fun

• $on_returned must be a fun which takes an argument, which is the result of $body

• $on_raised must be a fun which takes (Msg, Traces) of an exception raised in the invocation of $body

CONTROL.try invokes $body with no args.

If the invocation of $body terminates without raising an exception, CONTROL.try tail-calls $on_returned with the result of the invocation of $body.

If the invocation of $body terminates raising an exception, CONTROL.try tail-calls $on_raised with the message str and a vec of the traces.

Example:

:TRACE.require_from('kink/')
:CONTROL.require_from('kink/')

:attempt_divide_10_by <- {(:Divisor)
  CONTROL.try(
    { 10 // Divisor }
    {(:Result)
      stdout.print_line('result: {}'.format(Result))
    }
    {(:Msg :Traces)
      TRACE.format_traces(Msg Traces).each{(:Line)
        stdout.print_line(Line)
      }
    }
  )
}

attempt_divide_10_by(2)
# Output:
#   result: 5

attempt_divide_10_by(0)
# Output:
#   exception traces: from oldest to newest
#   [launch]
#   [builtin:kink-mods/kink/_launch/LAUNCH_KINK.kn L196 C5 try] }.-->try(
#   [reset]
#   [builtin:kink-mods/kink/_launch/LAUNCH_KINK.kn L195 C5 start] -->start(Non_opts)
#   [builtin:kink-mods/kink/_launch/LAUNCH_KINK.kn L131 C19 if_else] Non_opts.empty?.-->if_else(
#   [builtin:kink-mods/kink/_launch/LAUNCH_KINK.kn L139 C7 _run_script] -->_run_script($script_fun Script_args)
#   [builtin:kink-mods/kink/_launch/LAUNCH_KINK.kn L122 C3 script_fun] -->script_fun(Env)
#   [(stdin) L22 C1 attempt_divide_10_by] -->attempt_divide_10_by(0)
#   [(stdin) L5 C11 try] CONTROL.-->try(
#   [builtin:kink-mods/kink/CONTROL.kn L199 C14 reset] :handler = -->reset('kink/CONTROL-try'){
#   [builtin:kink-mods/kink/CONTROL.kn L200 C10 body] :R = -->body
#   [(stdin) L6 C10 op_intdiv] { 10 -->// Divisor }
#   exception message: Num.op_intdiv: zero division: 10 is divided by 0

3.4.4. CONTROL.while($cond $body)

CONTROL.while calls $body repeatedly while $cond returns true.

Preconditions:

• $cond must be a thunk fun which returns a bool

• $body must be a thunk fun

CONTROL.while first calls $cond with no args. If $cond returns true, CONTROL.while calls $body with no args. It repeats until Fun returns false.

Example:

:CONTROL.require_from('kink/')

:Vec <- [1 2 3]
CONTROL.while{ Vec.nonempty? }{
  :N = Vec.pop_front
  stdout.print_line(N.repr)
}
# Output:
#   1
#   2
#   3

3.4.5. CONTROL.with_on_exit($body)

CONTROL.with_on_exit calls $body with $on_exit fun. $on_exit takes a thunk, which is registered as a cleanup thunk of the subsequent calculation in the invocation of CONTROL.with_on_exit. The result of $body will be the result of CONTROL.with_on_exit.

Typical usage of CONTROL.with_on_exit is releasing resources safely.

For example:

:CONTROL.require_from('kink/')
:FILE.require_from('kink/io/')

:Text <- CONTROL.with_on_exit{(:on_exit)
  :In = FILE.open_to_read('/path/to/file')
  on_exit{ In.close }
  In.read_all_bin.decode('UTF-8')
}
stdout.print(Text)  # prints the text in /path/to/file

In the example above, In is closed when exiting from CONTROL.with_on_exit, no matter if read_all_bin returns normally or raises an exception.

3.4.6. CONTROL.with_break($body)

CONTROL.with_break calls $body with $break fun. $break takes a val. If $break is called in the invocation of $body, it escapes from the invocation of CONTROL.with_break, and CONTROL.with_break returns the arg of $break.

Example:

:CONTROL.require_from('kink/')
:Num <- 35
:Result <- CONTROL.with_break{(:break)
  (Num % 15 == 0).if_true{ break('fizzbuzz') }
  (Num % 3 == 0).if_true{ break('fizz') }
  (Num % 5 == 0).if_true{ break('buzz') }
  Num.show
}
stdout.print_line(Result)  # => buzz