imports

{-# LANGUAGE RankNTypes #-} module Plutarch.Docs.DataAndScottEncoding (nothing, just, foo) where import Prelude (Integer, (+))

Data encoding and Scott encoding

In Plutus Core, there are multiple (conflicting) ways to represent non-trivial ADTs: Constr data encoding, Scott encoding, or native SoP added in UPLC 1.1.0. 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, and PMap (actually just a builtin list of builtin pairs). It's important to note that Data (Constr or otherwise) is also a builtin type.

Data encoding

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.

Scott 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!

SoP Encoding

SoP stands for Sum of Product and it is another way of encoding data types. UPLC introduced these as a native construct with two new UPLC constructors, Constr that encodes a numerical tag (that is used to encode which constructor is used) and a list of terms (that represent fields of the constructor) and Case - an eliminator that given a Vector of terms that handle each of the constructors and all evaluate to the same value.

To use SoP datatype representation you can use DeriveAsSOPStruct deriving via helper, see how Maybe is doing it in PlutusType section