Skip to content

Compiler Primitives, Builtin Namespaces, and Prelude

gnumonik edited this page Jan 20, 2025 · 3 revisions

Preamble

Purus provides out-of-the-box support for Plutus Builtins, PlutusData serialization/deserialization functions, and some other miscellaneous utility functions.

The Builtin Namespace

Purus supports all Plutus DefaultUni builtins, with types and function names that should be intuitive to those familiar with the Haskell implementation, in a Builtin namespace.

The Builtin module is always implicitly imported. That means that you never need to add

import Builtin

If you wish to use the modules. For example, this is a valid Purus module:

module Main where 

main :: Int 
main = Builtin.addInteger 1 2

Purus supports the following types under the Builtin namespace:

  • BuiltinData
  • BuiltinPair
  • BuiltinList
  • `BuiltinByteString
  • BuiltinElementG1
  • BuiltinElementG2
  • BuiltinM1Result

And it supports the full suite of Builtin functions, given here with their Purus types:

  • addInteger :: Int -> Int -> Int
  • subtractInteger :: Int -> Int -> Int
  • multiplyInteger :: Int -> Int -> Int
  • divideInteger :: Int -> Int -> Int
  • quotientInteger :: Int -> Int -> Int
  • remainderInteger :: Int -> Int -> Int
  • modInteger :: Int -> Int -> Int
  • equalsInteger :: Int -> Int -> Boolean
  • lessThanInteger :: Int -> Int -> Boolean
  • lessThanEqualsInteger :: Int -> Int -> Boolean
  • appendByteString :: BuiltinByteString -> BuiltinByteString -> BuiltinByteString
  • consByteString :: Int -> BuiltinByteString -> BuiltinByteString
  • sliceByteString :: Int -> Int -> BuiltinByteString -> BuiltinByteString
  • lengthOfByteString :: BuiltinByteString -> Int
  • indexByteString :: BuiltinByteString -> Int -> Int
  • equalsByteString :: BuiltinByteString -> BuiltinByteString -> Boolean
  • lessThanByteString :: BuiltinByteString -> BuiltinByteString -> Boolean
  • lessThanEqualsByteString :: BuiltinByteString -> BuiltinByteString -> Boolean
  • sha2_256 :: BuiltinByteString -> BuiltinByteString
  • sha3_256 :: BuiltinByteString -> BuiltinByteString
  • blake2b_256 :: BuiltinByteString -> BuiltinByteString
  • verifyEd25519Signature :: BuiltinByteString -> BuiltinByteString -> BuiltinByteString -> Boolean
  • verifyEcdsaSecp256k1Signature :: BuiltinByteString -> BuiltinByteString -> BuiltinByteString -> Boolean
  • verifySchnorrSecp256k1Signature :: BuiltinByteString -> BuiltinByteString -> BuiltinByteString -> Boolean
  • appendString :: String -> String -> String
  • equalsString :: String -> String -> Boolean
  • encodeUtf8 :: String -> BuiltinByteString
  • decodeUtf8 :: BuiltinByteString -> String
  • ifThenElse :: forall (a :: Type). Boolean -> a -> a -> a SEE NOTE BELOW ON BOOLEANS AND IFTE
  • chooseUnit :: forall (a :: Type). Unit -> a -> a
  • trace :: forall (a :: Type). String -> a -> a
  • fstPair :: forall (a :: Type) (b :: Type). BuiltinPair a b -> a
  • sndPair :: forall (a :: Type) (b :: Type). BuiltinPair a b -> b
  • chooseList :: forall (a :: Type) (b :: Type). BuiltinList a -> b -> b SEE NOTE BELOW ON BUILTINLIST & LIST
  • mkCons :: forall (a :: Type). a -> BuiltinList a -> BuiltinList a
  • headList :: forall (a :: Type). BuiltinList a -> a
  • tailList :: forall (a :: Type). BuiltinList a -> BuiltinList a
  • nullList :: forall (a :: Type). BuiltinList a -> Boolean
  • chooseData :: forall (a :: Type). BuiltinData -> a -> a -> a -> a -> a
  • constrData :: Int -> BuiltinList BuiltinData -> BuiltinData
  • mapData :: BuiltinList (BuiltinPair BuiltinData BuiltinData) -> BuiltinData
  • listData :: BuiltinList BuiltinData -> BuiltinData
  • iData :: Int -> BuiltinData
  • bData :: BuiltinByteString -> BuiltinData
  • unConstrData :: BuiltinData -> BuiltinPair (Int,BuiltinList BuiltinData)
  • unMapData :: BuiltinData -> BuiltinList (BuiltinPair BuiltinData BuiltinData)
  • unListData :: BuiltinData -> BuiltinList BuiltinData
  • unIData :: BuiltinData -> Int
  • unBData :: BuiltinData -> BuiltinByteString
  • equalsData :: BuiltinData -> BuiltinData -> Boolean
  • serializeData :: BuiltinData -> BuiltinByteString
  • mkPairData :: BuiltinData -> BuiltinData -> BuiltinPair BuiltinData BuiltinData
  • mkNilData :: Unit -> BuiltinList BuiltinData
  • mkNilPairData :: Unit -> BuiltinList (BuiltinPair BuiltinData BuiltinData)
  • integerToByteString :: Boolean -> Int -> Int -> BuiltinByteString
  • byteStringToInteger :: Boolean -> BuiltinByteString -> Int
  • All of the bls12* primitives are supported as well, but are omitted here for the sake of brevity.

NOTE: Booleans and IFTE

While this is a technical detail of compiler implementation that most users need not concern themselves with, in certain contexts it may be important to know that *Purus uses the SOP (sum of products) representation for Booleans everywhere to preserve consistency and does not expose any BuiltinBoolean type to users. To interface with the builtins, we insert shim code that converts to and from the SOP boolean to the (internal) BuiltinBoolean.

Additionally, the if-then-else language construct should be preferred over `Builtin.ifThenElse where possible. The language construct does not compile to the same UPLC as the Builtin, and (for reasons that should be familiar to Plutus developers but which are not worth explaining in detail here), you almost certainly want the behavior of the language construct.

NOTE: List and BuiltinList

Similar to the above remark, BuiltinList and the primitive Purus language List type are not equivalent. The BuiltinList type is only capable of containing elements which are PlutusData-encoded, whereas the language construct is an SOP-encoded algebraic list datatype that can contain Purus values of any Purus type.

Primitive Types & "Magical Functions"

The Purus compiler exposes a small set of primitive types:

  • Type (the kind of values)
  • Constraint (the kind of constraints)
  • Row (the kind of rows of types)
  • Function / -> (the function type)
  • List (An algebraic data type representation of lists that can contain Purus values of any single type)
  • Record (Typically used with the {field1 :: Foo, field2 :: Bar} sugar)
  • String (This is NOT a list of characters as in Haskell)
  • Char
  • Int (Unbounded, corresponds to Haskell's Integer, not to Haskell's Int)
  • Boolean (Algebraic data type representation)
  • Unit (corresponds to PureScript's Unit and can be used in Builtin functions directly)
  • Delayed (representation of a 'lazy' value)

List and Boolean need some further explanation. Neither of these is equivalent to their Builtin version, and both are defined as algebraic datatypes internally in the compiler. Though these declarations never appear anywhere in a Purus source file, you can regard them as:

data List (a :: Type) = Nil | Cons a (List a) 

data Boolean = True | False 

The fact that these never appear in a source file does not imply that they cannot be used as normal datatypes. You can match on their constructors, construct values of them, etc, in the same way as with every other algebraic datatypes. All of the following expressions are OK:

consOne :: List Int -> List Int 
consOne xs = Cons 1 xs 

emptyInts :: List Int 
emptyInts = Nil 

andFromIFTE :: Boolean -> Boolean -> Boolean 
andFromIFTE b1 b2 = if b1 then (if b2 then True else False) else False  

Tuples

The Purus compiler automatically generates data declarations in the Prim namespace for tuples up to the configurable tuple limit. (You can adjust this limit by modifying the maxTupleSize value in the Language.Purus.Config module. The default is 14, which suffices to cover translation of all of the ledger types).

These declarations look like:

data Tuple1 a = Tuple1 a

data Tuple2 a b = Tuple2 a b

data Tuple3 a b c = Tuple3 a b c

and so on. Users can make use of these types directly, and there is nothing particularly special about them (other than the fact that Purus supports them by default out the box, whereas PureScript does not).

Ledger Types

Plutus ledger types are defined internally and exposed as part of the Prim namespace. They can be examined here

"Magical" Functions

In addition to these primitive types, Purus implicitly imports (both qualified as Prim and unqualified) a variety of helper functions under the Prim namespace. Of primary importance to users are the "magical" functions, which are neither Builtin functions defined in Plutus nor PureScript functions that could be defined in a PureScript source file. Those functions are:

  • delay :: forall (a :: Type). a -> Delayed a
  • force :: forall (a :: Type). Delayed a -> a
  • error :: forall (a :: Type). a

These functions work in the manner that you would expect. Under the hood, Delayed a is just

newtype Delayed a = Delayed (Unit -> a)

But you cannot match on the constructor to get out the inner function - think of this is as a newtype where the constructor is not exported, and of force/delay as "smart constructors/destructors".

The Prelude

Purus also contains a host of helper functions and types for writing Plutus programs under the Prim namespace. While these functions are not truly primitive (i.e. they have definitions in a PureScript source file), a bug/limitation of the Haskell compiler means that we cannot place all of the declarations in a single module.

While we intend to generate ergonomic documentation for these modules, we need to work out a few kinks first. In the meantime, you can observe the contents of the Prelude in these three source files:

Again, PLEASE NOTE THAT THESE MODULES GET COMBINED AND BUNDLED INTO THE Prim NAMESPACE. They should be imported implicitly in every Purus module in both the unqualified and qualified form.

Clone this wiki locally