imports

{-# OPTIONS_GHC -Wno-redundant-constraints #-}
{-# LANGUAGE UndecidableInstances #-}
module Plutarch.Docs.PlutusTypePConAndPMatch (PMyType(..), PMyTypeData(..)) where
import Data.Kind (Type)
import GHC.Generics (Generic)
import Generics.SOP qualified as SOP
import Plutarch.Prelude
import Plutarch.Repr.SOP (DeriveAsSOPStruct (DeriveAsSOPStruct))

PlutusType

PlutusType is the primary typeclass that determines the underlying representation for a Plutarch type. It lets you construct and deconstruct Plutus Core constants from a Plutarch type's constructors (possibly containing other Plutarch terms).

NOTE: It's essentially a combination of PCon (for term construction) and PMatch (for term deconstruction). Nowadays, PCon and PMatch are actually both just an alias for PlutusType and you'll get a deprecation warning if you use them.

class PlutusType (a :: S -> Type) where
  {-
    snip
  -}
  pcon' :: forall s. a s -> Term s (PInner a)
  default pcon' :: DerivePlutusType a => forall s. a s -> Term s (PInner a)
  pcon' = let _ = witness (Proxy @(PlutusType a)) in derivedPCon

  pmatch' :: forall s b. Term s (PInner a) -> (a s -> Term s b) -> Term s b
  default pmatch' :: DerivePlutusType a => forall s b. Term s (PInner a) -> (a s -> Term s b) -> Term s b
  pmatch' = derivedPMatch

Note: You don't need to look too much into the types! After all, you'll be using pcon and pmatch, rather than pcon' and pmatch'. PInner is meant to represent the "inner" type of a - the Plutarch type representing the Plutus Core constant used to represent a.

You should always use pcon and pmatch instead of pcon' and pmatch' - these are provided by the PCon and PMatch typeclasses:

Another feature of PlutusType instances is that you can extract out the inner type of any PlutusType instance! Above, the inner type (or representation) of PMaybe was a function. You can use pto to safely take this inner type out-

pto :: Term s a -> Term s (PInner a)

This is quite useful when working with newtypes. Notice how PCurrencySymbol, for example, is simply a newtype to a PByteString. Its PInner is also PByteString. To be able to use functions that operate on PByteStrings with your PCurrencySymbol, you can simply take out the PByteString using pto!

Implementing PlutusType for your own types (Scott Encoding)

If you want to represent your data type with Scott encoding (and therefore don't need to make it Data encoded), you should simply derive it generically:

data PMyType (a :: S -> Type) (b :: S -> Type) (s :: S)
  = POne (Term s a)
  | PTwo (Term s b)
  deriving stock (Generic)
  deriving anyclass PlutusType

instance DerivePlutusType (PMyType a b) where type DPTStrat _ = PlutusTypeScott

NOTE: you can derive PlutusType for all types you defined a DerivePlutusType instance for. The strategy it uses is determined by the type that you put after DPTStrat _ =, in this case Scottencoding

Implementing PlutusType for your own types (Data Encoding)

If your type is supposed to be represented using Data encoding instead, you can derive PlutusType via PlutusTypeData:

data PMyTypeData (a :: S -> Type) (b :: S -> Type) (s :: S)
  = POneD (Term s (PDataRecord '[ "_0" ':= a ]))
  | PTwoD (Term s (PDataRecord '[ "_0" ':= b ]))
  deriving stock Generic
  deriving anyclass (PlutusType)

instance DerivePlutusType (PMyTypeData a b) where type DPTStrat _ = PlutusTypeData

Implementing PlutusType for your own types (SoP Encoding)

Mechanism is the same as in Scott encoding case but using DeriveAsSOPStruct instead.

NOTE: You must import DeriveAsSOPStruct with constructor (like imports at the top of this page do) for GHC to see that the helper is coercible to your own type.

data PMaybeSoP (a :: S -> Type) (s :: S)
  = PJustSoP (Term s a)
  | PNothingSoP
  deriving stock (Generic)
  deriving anyclass (SOP.Generic, PEq, PShow)

deriving via DeriveAsSOPStruct (PMaybeSoP a) instance PlutusType (PMaybeSoP a)

Implementing PlutusType for your own types (newtype)

See: DPTStrat PlutusTypeNewtype.