imports
module Plutarch.Docs.UsingHaskellLevelFunctions (pelimList, pelimList') where
import Data.Kind (Type)
import Plutarch.Prelude hiding (pelimList)
When to use Haskell level functions?
Although you should generally prefer Plutarch level functions, there are times when a Haskell level function is actually much better. However, figuring out when that is the case - is a delicate art.
There is one simple and straightforward use case though, when you want a function argument to be lazily evaluated. In such a case, you should use a Haskell level function that pdelay
s the argument before calling some Plutarch level function. Recall that Plutarch level functions are strict.
Outside of that straightforward use case, figuring out when to use Haskell level functions is quite complex. Haskell level functions will always be inlined when generating the Plutus Core. Unless the function is used only once, this sort of inlining will increase the script size - which is problematic.
However, if the function is used only once, and making it Plutarch level causes extra plam
s and #
s to be introduced - you should just make it Haskell level. For example, consider the pelimList
implementation:
pelimList :: PElemConstraint PBuiltinList a => Term s (a :--> PBuiltinList a :--> r) -> Term s r -> Term s (PBuiltinList a) -> Term s r
pelimList match_cons match_nil ls = pmatch ls $ \case
PCons x xs -> match_cons # x # xs
PNil -> match_nil
It takes in a Plutarch level function, let's see a typical usage:
pelimList
(plam $ \x xs -> pcons # x # (self # xs))
pnil
ls
This is rather redundant, the above snippet will translate to:
pmatch ls $ \case
PCons x xs -> (plam $ \x xs -> pcons # x # (self # xs)) # x # xs
PNil -> match_nil
Extra plam
s and #
s have been introduced. Really, pelimList
could have taken a Haskell level function instead:
pelimList' ::
forall (a :: S -> Type) (r :: S -> Type) (s :: S).
PElemConstraint PBuiltinList a
=> (Term s a -> Term s (PBuiltinList a) -> Term s r)
-> Term s r -> Term s (PBuiltinList a)
-> Term s r
pelimList' match_cons match_nil ls = pmatch ls $ \case
PCons x xs -> match_cons x xs
PNil -> match_nil
Now, the following usage:
pelimList
(\x xs -> pcons # x # (self # xs))
pnil
ls
would turn into:
pmatch ls $ \case
PCons x xs -> pcons # x # (self # xs)
PNil -> match_nil
It turns out that using pelimList
almost always involves using a one-off Haskell level function (and therefore a redundant plam
). As such, pelimList
benefits greatly from just taking a Haskell level function directly.
However, not all higher order functions benefit from taking Haskell level functions. In many higher order function use cases, you could benefit from passing a commonly used function argument, rather than a one-off function argument. Imagine map
, you don't always map with one-off functions - often, you map
with existing, commonly used functions. In these cases, that commonly used function ought to be a Plutarch level function, so it can be hoisted and map
can simply reference it.