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:
xcertainly cannot exist as a variable. Because whenfis called,xis replaced by an integer constant, which needs to go into theCompiledCode. In other words, when we callf 42, we'd expect it to return aCompiledCodecorresponding to42 + 1.- But
xcannot 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
xis 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 ifxwas 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.Liftinstance). ↩