Using foldMap

Code snippet from the lecture.

1  tx :: TxConstraints Void Void
2  tx = mconcat [mustSpendScriptOutput oref $ Redeemer $ PlutusTx.toData () | oref <- orefs]
3       <> mustValidateIn (from now)

Extracting line 2 as a function with types

makeUtxoConstraints :: [TxOutRef] -> TxConstraints Void Void
makeUtxoConstraints orefs = mconcat [mustSpendScriptOutput oref $ Redeemer $ PlutusTx.toData () | oref <- orefs]

Note

The following code snippets run in cabal REPL via the command cabal repl and run in the context of the nix-shell. The following commands need to be run to import modules and enable features

Prelude> :set -XTypeApplications
Prelude> :m +Data.Void Ledger.Constraints.TxConstraints Plutus.V1.Ledger.Tx
Prelude Data.Void Ledger.Constraints.TxConstraints Plutus.V1.Ledger.Tx> :set -fprint-explicit-foralls

TxConstraints is defined by the following, on line 10 note it has an instance of Monoid

 1 Prelude Data.Void Ledger.Constraints.TxConstraints Plutus.V1.Ledger.Tx> :i TxConstraints
 2 type TxConstraints :: * -> * -> *
 3 data TxConstraints i o
 4 = TxConstraints {txConstraints :: [TxConstraint],
 5                 txOwnInputs :: [InputConstraint i],
 6                 txOwnOutputs :: [OutputConstraint o]}
 7         -- Defined in ‘Ledger.Constraints.TxConstraints’
 8 instance forall i o. (Eq i, Eq o) => Eq (TxConstraints i o)
 9 -- Defined in ‘Ledger.Constraints.TxConstraints’
10 instance forall i o. Monoid (TxConstraints i o)
11 -- Defined in ‘Ledger.Constraints.TxConstraints’
12 instance forall i o. Semigroup (TxConstraints i o)
13 -- Defined in ‘Ledger.Constraints.TxConstraints’
14 instance forall i o. (Show i, Show o) => Show (TxConstraints i o)
15 -- Defined in ‘Ledger.Constraints.TxConstraints’
16 Prelude Data.Void Ledger.Constraints.TxConstraints>

The definition of Monoid is as follows

class Semigroup a => Monoid a where
    mempty :: a
    mappend :: a -> a -> a
    mconcat :: [a] -> a

The most basic implementation of a Monoid is for a List via concatenation.

It provides functionality to combine a given type, so for [a] it will be able to combine two lists using the concatenation operator ++

instance Semigroup [a] where
    (<>) :: [a] -> [a] -> [a]
    (<>) = (++)

instance Monoid [a] where
    mempty :: [a]
    mempty = []
    mconcat :: [[a]] -> [a]
    mconcat xss = [x | xs <- xss, x <- xs]

TxConstraints can also be combined using foldMap since it has an instance of Monoid

foldMap uses the methods of Monoid indirectly rather than explictly using mconcat

Heres is the type signature for foldMap

Prelude Data.Void Ledger.Constraints Plutus.V1.Ledger.Tx> :t +v foldMap
foldMap
  :: forall (t :: * -> *) m a.
     (Foldable t, Monoid m) =>
     (a -> m) -> t a -> m

With TypeApplications we can see the specialised version which needs to be used in the code.

Here Foldable is a List and the Monoid is TxConstraints Void Void

Prelude Data.Void Ledger.Constraints.TxConstraints Plutus.V1.Ledger.Tx> :t +v foldMap @[] @(TxConstraints Void Void) @TxOutRef
foldMap @[] @(TxConstraints Void Void) @TxOutRef
  :: Monoid (TxConstraints Void Void) =>
      (TxOutRef -> TxConstraints Void Void)
      -> [TxOutRef] -> TxConstraints Void Void

Refactored code from the lecture

tx :: TxConstraints Void Void
tx = foldMap (\oref -> mustSpendScriptOutput oref $ Redeemer $ PlutusTx.toData ()) orefs
      <> mustValidateIn (from now)

or we can avoid using the lambda using mustSpendScriptOutput in infix form

tx :: TxConstraints Void Void
tx = foldMap (`mustSpendScriptOutput` redeemer) orefs
      <> mustValidateIn (from now)
         where
              redeemer = Redeemer $ PlutusTx.toData ()