Skip to content
Discussions/App Development/Can I use monad transformers inside Daml choicesForum ↗

Can I use monad transformers inside Daml choices

App Development4 posts204 views2 likesLast activity Oct 2022
RE
rexngOP
Oct 2022

To simplify error handling without littering the code with arrowheads like

nonconsuming choice Foo : Either Text ()
  controller Bar
  do
    case foo of
      Left err -> pure $ Left err
      Right x ->
         -- do something
         case bar of
            Left err -> pure $ Left err
            Right y ->
               -- do something else

I have created an EitherT monad transformer to hide some of the clutter with the Left branches.

newtype EitherT m a b = EitherT { runEitherT : m (Either a b) }

instance Functor m => Functor (EitherT m a) where
  fmap f = EitherT . fmap (fmap f) . runEitherT

instance Action m => Applicative (EitherT m a) where
  pure = EitherT . pure . pure
  EitherT meF <*> EitherT meX = EitherT $ do
    eF <- meF
    case eF of
      Left err -> return (Left err)
      Right f -> do
        eX <- meX
        case eX of
          Left err -> return (Left err)
          Right x -> return $ Right (f x)

instance Action m => Action (EitherT m a) where
  EitherT meX >>= f = EitherT $ do
    eX <- meX
    case eX of
      Left err -> return (Left err)
      Right x -> runEitherT (f x)

However, this doesn’t seem to work in choices as this gives me a compile error:

nonconsuming choice Foo : Either Text ()
  controller Bar
  do
      let lookupOptUpdate = lookupByKey @MyTemplate myKey
          lookupEitherUpdate = fmap (optionalToEither "some text") lookupOptUpdate
          lookupEitherT = EitherT lookupEitherUpdate

      lookup <- lookupEitherT -- Compile error

With the message:

Couldn't match type ‘EitherT Update Text’ with ‘Update’
      Expected type: Update (ContractId MyTemplate)
        Actual type: EitherT Update Text (ContractId MyTemplate)

Am I doing something wrong?

MA
Matteo_Limberto
Oct 2022

Hi @rexng.

Your binding lookupEitherT is of type EitherT Update Text (ContractId MyTemplate). However, a choice implementation is a computation within an Update monad. Hence, when you are using the <- operator, the object you are unwrapping must be of type Update ... .

In order to make it work, you first need to expose the Update by using runEitherT.

Two additional pointers for you:

  • you can find an implementation of the ExceptT monad transformer in the daml-ctl library

  • I used it similarly to simplify error handling for the TriggerA monad. You can have a look at the code here

In my example, I write all logic within the newly defined TriggerE monad, where type TriggerE s = ExceptT Text (TriggerA s). Only at the very end, I unpack the result using

-- | Runs a TriggerE rule, using `debug` to print the exception
runAndDebug : (Party -> TriggerE s ()) -> Party -> TriggerA s ()
runAndDebug rule p =
  runExceptT (rule p) >>= f
  where
    f (Left msg) = debug msg
    f (Right ()) = pure ()

In your case you probably want to use abort instead of debug.

Hope this helps!

RE
rexng
Oct 2022

Thank you @Matteo_Limberto. That’s extremely helpful!

MA
Matteo_Limberto
Oct 2022

Kudos to @Luciano who taught me all of this stuff!

← Back to Discussions