notes

Useful lazy patterns in Whitespace

The mixed lazy and strict semantics of the reference interpreter are difficult to reason with, but sometimes have useful applications.

Exceptions as values

Since exceptions are lazily evaluated, this enables dataflow-dependent exceptions, through error-carrying numbers.

We can have a very basic algebra for combining errors: A guard is a tag, that produces an exception when it is evaluated, effectively enum Guard { Success, Fail }. Guard::Success is constructed with push 0 (SSSL) and Guard::Fail is constructed with push <empty> (SSL). Guard::and is simply add, which lazily combines two guards. Guard::unwrap is retrieve drop, to force evaluation of the guard, which is at the top of the stack, and produce an exception if it has Guard::Fail.

I don’t think there is a way to inspect values, to see if an error is contained, so Guard::or would not be possible without using a boolean flag, in which case, there is no use for the guard, as it can be trivially derived from the flag with a branch.

A higher-level data-carrying Result would be a simple wrapper over a guard, generated by a compiler.

Forcing evaluation

To force eager evaluation of a lazy expression and produce its value or exception, while avoiding IO effects, store, jz, or jn can be used.

store eagerly evaluates its address, so retrieve the value at that address, then store it back, so it is not clobbered. To avoid retaining references to the prefix of the heap via lazy retrieves, when running with the reference interpreter, use address 0.

With sub:

    ^ - 0 retrieve store

With mul:

    0* 0 retrieve store

The subtraction variant is encoded with 16 + z bytes and multiplication with 16 + 2z, where z is 1 for implementations that require a sign bit for zero values and 0 otherwise.

Conditional branches jz and jn eagerly evaluate their condition. Simply branch to the next instruction in both cases.

With jz:

    jz .next
.next:

With jn:

    jn .next
.next:

These both require 6 + 2l bytes, where l is the number of bytes in the label. If labels are assigned in increasing numeric order, this will be shorter than the sub variant for programs with less than 32 or 64 other labels, depending on z.