imports
module Plutarch.Docs.Introduction (hf) where
import Plutarch.Prelude
Plutarch is an eDSL in Haskell for writing on-chain scripts for Cardano. With some caveats, Plutarch is a simply-typed lambda calculus (or STLC). Writing a script in Plutarch allows us to leverage the language features provided by Haskell while retaining the ability to compile to compact Untyped Plutus Core (or UPLC, which is an untyped lambda calculus).
When we talk about "Plutarch scripts," we are referring to values of type Term (s :: S) (a :: PType)
. Term
is a newtype
wrapper around a more complex type, the details of which Plutarch end-users can ignore. A Term
is a typed lambda term; it can be thought of as representing a computation that, if successfully evaluated, will return a value of type a
.
The two type variables of the Term s a
declaration have particular kinds:
s :: S
is like thes
ofST s a
. It represents the computation context in a manner that mimics mutable state while providing a familiar functional interface. Sections 1 through 4 of [1] give an accessible introduction to how this works.s
is never instantiated with a concrete value; it is merely a type-level way to ensure that computational contexts remain properly encapsulated (i.e., different state threads don't interact). For more in-depth coverage of this and other eDSL design principles used in Plutarch, see [2].a :: PType
is short-hand for "Plutarch Type". We prefix these types with a capitalP
, such asPInteger
,PBool
, and so forth. Tagging aTerm
with aPType
indicates the type of theTerm
's return value. Doing this allows us to bridge between the simple type system of Plutarch and the untyped UPLC.
Note that we should not think of a type of kind PType
as carrying a value; it is a tag for a computation that may produce a value. For instance, the definition of PInteger
is simply
data PInteger s
That is, there are no data constructors. If a value of type Term s PInteger
successfully executes the computation within context s
, the value computed will be an integer. We will never encounter values such as y :: PInteger; y = 3
; they simply do not exist. While readers new to Plutarch may need some time to fit this into their mental model, it is a crucial distinction to keep in mind.
For brevity, we will say a "value of type
Term s a
will evaluate to (...)". This phrase will carry two implicit notions: one, thatTerm s a
represents a computation executed in the contexts
; two, evaluatingTerm s a
is not guaranteed to succeed.
In brief, when writing Plutarch scripts, we have a few tasks:
- A.) Defining Plutarch Types (or
PType
s). We prefix these types with a capitalP
, such asPInteger
,PMaybe a
,PBool
, and so forth. As previously mentioned, these form the "tags" for PlutarchTerm
's, representing the type of the result of compiling and evaluating a Plutarch Script. - B.) Working with Plutarch
Terms
, which are values of the typeTerm (s :: S) (a :: PType)
. These are the Plutarch scripts themselves, from which we build up more complex scripts before compiling and executing them on-chain. - C.) Writing Haskell-level functions between Plutarch Terms (i.e., with types like
Term s a -> Term s b
). Doing so allows us to leverage Haskell's language features and ecosystem. - D.) Efficiently Converting the functions from (C.) to Plutarch-level functions, which are of the type
Term s (a :--> b)
. We can directly convert the functions from (C.) to Plutarch-level functions at the most naive level usingplam
. Additional Plutarch utilities provide for optimization opportunities. - E.) Compiling and executing the functions from (D.), targeting UPLC for on-chain usage.
As a preview, the bridge Plutarch provides between Haskell and UPLC looks something like this:
------------------------------------------------------
| *Haskell World* |
------------------------------------------------------
| Values with types like `Bool`, `Integer`, `Maybe a`|
| and regular Haskell functions like a -> b |
------------------------------------------------------
^ |
(functions like `plift`)--| |--(functions like `pconstant` or `plam`)
| |
| v (`pcon`)
------------------------------------------------------- | -------------------------------------------------------
| *Plutarch Term World* | <----------------------- | *Plutarch Type World* |
------------------------------------------------------- -------------------------------------------------------
| STLC terms; constants like `Term s PInteger` and | -----------------------> | Types like `PInteger`, `PMaybe a` |
| lambdas like `Term s (PInteger :--> PBool)` | | | |
------------------------------------------------------- (`pmatch`) -------------------------------------------------------
|
|
|--(`compile`)
|
|
v
-------------------------------------------------------
| *UPLC World* |
-------------------------------------------------------
| Untyped lambda calculus terms. Values of type `Data`|
| |
-------------------------------------------------------
Further, you may notice two general categories of functions in Plutarch: "Haskell-level" functions between terms, and "Plutarch-level"
functions as lambda terms. By convention, we will prefix the Haskell-level functions with h
and the Plutarch-level lambdas
with p
, for example
-- This example is listed here as a preview; the unfamiliar parts will
-- be detailed below.
-- A Plutarch-level lambda term
pf :: Term s (a :--> b :--> c)
pf = undefined
-- Recovering a Haskell level function from a Plutarch level function
hf :: Term s a -> Term s b -> Term s c
hf x y = pf # x # y
Note that pf
is truly just a Plutarch Term
and should not be treated specially.
The remainder of this document cover the bridge between Haskell and Plutarch at a high level. It will not cover all techniques necessary to write production-ready scripts. Nor will it cover the bridge between Plutarch and UPLC beyond the minimum. Nonetheless, it should provide sufficient background to prepare the reader for further study.
Sections:-