imports

module Plutarch.Docs.DelayAndForce (hif, pif') where 
import Plutarch.Prelude

Strictness and Laziness; Delayed Terms and Forcing

Plutarch, like UPLC, is strict by default; this is in contrast to Haskell, which is non-strict by default (often called "lazy"). This means that evaluating a function application in Plutarch (#) always evaluates the argument before executing the function.

This behavior may be undesirable. For example, it is usually unwanted to evaluate both the then and else branches of an if function, for efficiency reasons.

The Plutarch level function pif' is naturally strict in its arguments, so it does exactly that. For the purpose of this chapter we take pif' as a given, and create the lazy version pif based on that.

Note: The example below does not correspond to the actual implementations of pif or pif'. It is for pedagogic purposes only.

-- | Strict if-then-else.
pif' :: Term s (PBool :--> a :--> a :--> a)

To prevent evaluation of a term when it gets used as an argument in a function application, we can use pdelay to mark the argument term as delayed. On the type-level, it wraps the PType tag of a Term, as can be seen in its type signature.

pdelay :: Term s a -> Term s (PDelayed a)

The pforce function is the inverse to that, it converts a delayed term such that it gets evaluated when used in a function application (#). Forcing a term strips the PDelayed wrapper on the type-level:

pforce :: Term s (PDelayed a) -> Term s a

We now have the tools needed to create the lazy pif based on pif':

-- | Utilizing Haskell level functions with `pdelay` and `pforce` to have lazy wrapper around `pif`.
hif :: Term s PBool -> Term s a -> Term s a -> Term s a
hif cond whenTrue whenFalse = pforce $ pif' # cond # pdelay whenTrue # pdelay whenFalse

A word of caution: Calling pforce on the same delayed term in multiple different places can lead to duplicate evaluation of the term. Users familiar with Haskell's handling of laziness -- where forcing a thunk twice never duplicates computation -- should note that UPLC behaves differently.

Note that pdelay is not the only way to get lazy behavior. Haskell-level Term arguments, the branches of pmatch, and continuation functions on the Plutarch level are all naturally lazy.