imports
{-# OPTIONS_GHC -Wno-unused-imports #-}
{-# LANGUAGE TemplateHaskell #-}
module Plutarch.Docs.FFI () where
import Plutarch.Prelude
import Plutarch.FFI
import PlutusTx qualified
import PlutusTx.Builtins qualified as PlutusTx
import PlutusTx.Builtins.Internal qualified as PlutusTx
import Generics.SOP qualified as SOP
import Data.Default (def)
Interoperability with PlutusTx
Note: This module needs the PlutusTx plugin. As it does not yet work under
ghc92
+, some of this documentation is not part of the CI, the documentation is up to date as of Plutarch 1.3.
If you already have a codebase built using PlutusTx, you can choose to
re-write only its critical parts in Plutarch and to call them from
PlutusTx. The function to use is Plutarch.FFI.foreignExport
:
doubleInPlutarch :: ClosedTerm (PInteger :--> PInteger)
doubleInPlutarch = plam (2 *)
doubleExported :: PlutusTx.CompiledCode (Integer -> Integer)
doubleExported = foreignExport def doubleInPlutarch
doubleUseInPlutusTx :: PlutusTx.CompiledCode Integer
doubleUseInPlutusTx = doubleExported `PlutusTx.applyCode` PlutusTx.liftCode 21
Alternatively, you may go in the opposite direction and call an existing
PlutusTx function from Plutarch using Plutarch.FFI.foreignImport
:
doubleInPlutusTx :: PlutusTx.CompiledCode (Integer -> Integer)
doubleInPlutusTx = $$(PlutusTx.compile [||(2 *) :: Integer -> Integer||])
doubleImported :: Term s (PInteger :--> PInteger)
doubleImported = foreignImport doubleInPlutusTx
doubleUseInPlutarch :: Term s PInteger
doubleUseInPlutarch = doubleImported # 21
Note how Plutarch type PInteger :--> PInteger
corresponds to Haskell
function type Integer -> Integer
. If the types didn't correspond, the
foreignExport
and foreignImport
applications wouldn't compile. The
following table shows the correspondence between the two universes of types:
Plutarch | Haskell |
---|---|
pa :--> pb | a -> b |
PTxList pa | [a] |
PTxMaybe pa | Maybe a |
PInteger | Integer |
PBool | BuiltinBool |
PString | BuiltinString |
PByteString | BuiltinByteString |
PBuiltinData | Data |
PUnit | BuiltinUnit |
PDelayed pa | Delayed a |
User-defined types
When it comes to user-defined types, you have a choice of passing their values
encoded as Data
or directly. In the latter case, you'll have to declare your
type twice with two kinds: as a Haskell Type
and as a Plutarch
PType
. Furthermore, both types must be instances of SOP.Generic
, as in this
example:
data SampleRecord = SampleRecord
{ sampleBool :: PlutusTx.BuiltinBool
, sampleInt :: PlutusTx.Integer
, sampleString :: PlutusTx.BuiltinString
}
deriving stock (Generic)
deriving anyclass (SOP.Generic)
data PSampleRecord (s :: S) = PSampleRecord
{ psampleBool :: Term s PBool
, psampleInt :: Term s PInteger
, psampleString :: Term s PString
}
deriving stock Generic
deriving anyclass (SOP.Generic, PlutusType)
instance DerivePlutusType PSampleRecord where type DPTStrat _ = PlutusTypeScott
With these two declarations in place, the preceding table can gain another row:
Plutarch | Haskell |
---|---|
PDelayed PSampleRecord | SampleRecord |
The reason for PDelayed
above is a slight difference in Scott encodings of
data types between Plutarch and PlutusTx. It means you'll need to apply
pdelay
to a PSampleRecord
value before passing it through FFI to Haskell,
and pforce
after passing it in the opposite direction.
This technique can be used for most data types, but it doesn't cover recursive
types (such as lists) nor data types with nullary constructors (such as
Maybe
). To interface with these two common Haskell types, use PTxMaybe
and
PTxList
types from Plutarch.FFI
. The module also exports the means to
convert between these special purpose types and the regular Plutarch PMaybe
and PList
.