imports
{-# LANGUAGE RankNTypes #-}
module Plutarch.Docs.DataAndScottEncoding (nothing, just, foo) where
import Prelude (Integer, (+))
In Plutus Core, there are really two (conflicting) ways to represent non-trivial ADTs: Constr
data encoding, or Scott encoding. You should use only one of these representations for your non-trivial types.
Aside: What's a "trivial" type? The non-data builtin types!
PInteger
,PByteString
,PBuiltinList
,PBuiltinPair
, andPMap
(actually just a builtin list of builtin pairs). It's important to note thatData
(Constr
or otherwise) is also a builtin type.
Constr
data is essentially a sum-of-products representation. However, it can only contain other Data
values (not necessarily just Constr
data, could be I
data, B
data etc.) as its fields. Plutus Core famously lacks the ability to represent functions using this encoding, and thus Constr
encoded values simply cannot contain functions.
Note: You can find out more about the deep details of
Data
/BuiltinData
at plutonomicon.
With that said, Data
encoding is ubiquitous on the chain. It's the encoding used by the ledger api types, it's the type of the arguments that can be passed to a script on the chain etc. As a result, your datums and redeemers must use data encoding.
On the opposite (and conflicting) end, is Scott encoding. The internet can explain Scott encoding way better than I can. But I'll be demonstrating Scott encoding with an example anyway.
Firstly, what good is Scott encoding? Well it doesn't share the limitation of not being able to contain functions! However, you cannot use Scott encoded types within, for example, your datums and redeemers.
Briefly, Scott encoding is a way to represent data with functions. The Scott encoded representation of Maybe a
would be:
(a -> b) -> b -> b
Just 42
, for example, would be represented as this function:
\f _ -> f 42
Whereas Nothing
would be represented as this function:
\_ n -> n
We covered construction. What about usage/deconstruction? That's also just as simple. Let's say you have a function, foo :: Maybe Integer -> Integer
, it takes in a Scott encoded Maybe Integer
, and adds 42
to its Just
value. If it's Nothing
, it just returns 0
.
type Maybe a = forall b. (a -> b) -> b -> b
just :: a -> Maybe a
just x = \f _ -> f x
nothing :: Maybe a
nothing = \_ n -> n
foo :: Maybe Integer -> Integer
foo mb = mb (\x -> x + 42) 0
How does that work? Recall that mb
is really just a function. Here's how the application of f
would work:
foo (just 1)
foo (\f _ -> f 1)
(\f _ -> f 1) (\x -> x + 42) 0
(\x -> x + 42) 1
43
foo nothing
foo (\_ n -> n)
(\_ n -> n) (\x -> x + 42) 0
0
Neat!
This is the same recipe followed in the implementation of PMaybe
. See its PlutusType impl!