Lifting Values into CompiledCode
Stage Constraints
The standard way of turning a
into CompiledCode a
is by compiling it (see Compiling Plinth).
This requires the definition of a
to be known at compile time.
As such, the following fails to compile:
f :: Integer -> CompiledCode Integer
f x = $$(compile [|| x + 1 ||])
This is not a Template Haskell stage error1, but the Plinth compiler imposes additional stage constraints on top of Template Haskell's.
To see why the above doesn't work, recognize that in the CompiledCode
it is supposed to produce:
x
certainly cannot exist as a variable. Because whenf
is called,x
is replaced by an integer constant, which needs to go into theCompiledCode
. In other words, when we callf 42
, we'd expect it to return aCompiledCode
corresponding to42 + 1
.- But
x
cannot exist as a constant either, becausecompile [|| x + 1 ||]
happens at compile time, and at compile time we don't yet know the value ofx
.
If you try to do this, you'll get a "no unfolding" error, meaning the Plinth compiler cannot proceed because the unfolding (definition) of x
is not available.
On the other hand, the following is perfectly fine:
f :: CompiledCode (Integer -> Integer)
f = $$(compile [|| \x -> x + 1 ||])
Now we are just compiling a regular lambda, and in the resulting CompiledCode
, x
is simply a lambda-bound variable.
The above can be summarized by the following rule for compiling Plinth Code:
- Any variable inside the quotation passed to
compile
(i.e., the...
in$$(compile [|| ... ||])
) must either be a top-level variable (which can be defined in the same module or imported from a different module), or bound inside the quotation.
Lifting
Similar to the Language.Haskell.TH.Syntax.Lift
class, which lifts Haskell values into Template Haskell ASTs, Plinth has a PlutusTx.Lift.Class.Lift
class which lifts Haskell values into CompiledCode
.
Both Lift
classes work in a similar way - for instance, Template Haskell's Lift
lifts integer 42 into a Template Haskell constant (LitE (IntegerL 42)
), while Plinth's Lift
lifts it into a PIR/UPLC constant (con integer 42
).
Lift
instances can generally be derived for data types that don't contain functions.
However, functions usually cannot be lifted.
To derive the Plinth Lift
instance for a data type, use makeLift.
Lift
makes it possible to write the above function of type Integer -> CompiledCode Integer
, which can be written as
f :: Integer -> CompiledCode Integer
f x = liftCodeDef (x + 1)
Now there's no Template Haskell, hence no compile time work being done.
When f
is called, x + 1
will be evaluated in Haskell into WHNF (i.e., an integer constant), which liftCodeDef
dutifully converts into a con integer
.
When you call f 42
, the resulting CompiledCode
contains a single constant, 43.
If you want the CompiledCode
to instead contain 42 + 1
, you can compile (+ 1)
separately, then apply it to the lifted x
:
f :: Integer -> CompiledCode Integer
f x = $$(compile [|| (+ 1) ||]) `unsafeApplyCode` liftCodeDef x
There are variants of liftCodeDef
and unsafeApplyCode
.
See Haddock for PlutusTx.Lift and Haddock for PlutusTx.Code for more details.
Footnotes
-
Here the usage of
x
is surrounded by one quotation ([|| ... ||]
) and one splice ($$(...)
), which is legit from the Template Haskell point of view. It would be a Template Haskell stage error ifx
was surrounded only by a splice (f x = $$(compile (x + 1))
) or only by a quotation (f x = compile [|| x + 1 ||]
, unlessx
's type has aLanguage.Haskell.TH.Syntax.Lift
instance). ↩