# Daml Triggers composed from small building blocks

**URL:** https://forum.canton.network/t/daml-triggers-composed-from-small-building-blocks/4460
**Category:** App Development
**Tags:** features and bugs, trigger
**Created:** [May 2, 2022, 8:42am UTC](https://forum.canton.network/t/daml-triggers-composed-from-small-building-blocks/4460 "2022-05-02T08:42:25Z")
**Posts on this page:** 3
**Page:** 1

<div class="post-metadata">

### Author: ![gyorgybalazsi](https://yyz2.discourse-cdn.com/flex034/user_avatar/forum.canton.network/gyorgybalazsi/32/684_2.png) [@gyorgybalazsi](https://forum.canton.network/u/gyorgybalazsi)
#### Post date: [May 2, 2022, 8:42am UTC](https://forum.canton.network/t/daml-triggers-composed-from-small-building-blocks/4460/1 "2022-05-02T08:42:25Z")

</div>

I want to compose Daml Triggers from small building blocks, based on the insight that `instance Action (TriggerA s)`, just like `instance Action Update` or `instance Action Script`.

This is how I decomposed and recomposed the `autoReply` trigger form the `gsg-trigger` template.

Can I do it even simpler and/or more elegantly?

```haskell
module ChatBot where

import qualified Daml.Trigger as T
import qualified User
import qualified DA.List.Total as List
import DA.Action (when)
import DA.Optional (whenSome)

import Daml.Script
import DA.Map qualified as Maps
import Daml.Trigger.Assert
import DA.Assert

-- THE ORIGINAL TRIGGER

autoReply : T.Trigger ()
autoReply = T.Trigger
  { initialize = pure ()
  , updateState = \_ -> pure ()
  , rule = \p -> do
      message_contracts <- T.query @User.Message
      let messages = map snd message_contracts
      debug $ "Messages so far: " <> show (length messages)
      let lastMessage = List.maximumOn (.receivedAt) messages
      debug $ "Last message: " <> show lastMessage
      whenSome lastMessage $ \m ->
        when (m.receiver == p) $ do
          users <- T.query @User.User
          debug $ "users: " <> show users
          let isSender = (\user -> user.username == m.sender)
          let replyTo = List.head $ filter (\(_, user) -> isSender user) users
          whenSome replyTo $ \(sender, _) ->
            T.dedupExercise sender (User.SendMessage p "Please, tell me more about that.")
  , registeredTemplates = T.AllInDar
  , heartbeat = None
  }

test : Script ()
test = do 
  alice <- allocateParty "Alice" 
  bob <- allocateParty "Bob" 

  aliceUserCid <- submit alice $ Daml.Script.createCmd User.User with username = alice, following = [bob]
  debug $ "Alide user cid: " <> show aliceUserCid
  bobUserCid <- submit bob $ Daml.Script.createCmd User.User with username = bob, following = [alice]
  debug $ "Bob user cid: " <> show bobUserCid

  bobMsgCid <- submit bob $ Daml.Script.exerciseCmd aliceUserCid User.SendMessage with sender = bob, content = "Hello Alice"
  
  let acsBuilder = toACS aliceUserCid <> toACS bobUserCid <> toACS bobMsgCid

  -- TESTING THE ORITINAL TRIGGER
  ((), cmds) <- testRule autoReply alice [] acsBuilder Maps.empty ()

  debug $ length cmds
  
  assertExerciseCmd (flattenCommands cmds) $ \(cid, choiceArg) -> do
    cid === bobUserCid
    debug choiceArg
    choiceArg === User.SendMessage with
      sender = alice
      content = "Please, tell me more about that."

-- COMPOSABLE BUILDING BLOCKS

queryMessages : T.TriggerA () [User.Message]
queryMessages =
  map snd <$> T.query @User.Message 

filterForReceivedByReplier : Party -> [User.Message] -> T.TriggerA () [User.Message]
filterForReceivedByReplier replyingParty messages = do 
  return $ filter (\m -> m.receiver == replyingParty) $ messages

pickLastMessage : [User.Message] -> T.TriggerA () (Optional User.Message)
pickLastMessage listOfUsers = do 
  return $ List.maximumOn (.receivedAt) $ listOfUsers 

checkIfReceivedByReplier : Party -> Optional User.Message -> T.TriggerA () (Optional User.Message)
checkIfReceivedByReplier replyingParty messageOpt = do 
  case messageOpt of 
    None -> return None 
    Some m -> return $ if m.receiver == replyingParty then Some m else None 

fromMessageToSenderUserCid : Optional User.Message -> T.TriggerA () (Optional (ContractId User.User))
fromMessageToSenderUserCid messageOpt = do 
  case messageOpt of 
    None -> return None 
    Some m -> do 
                userTuples <- T.query @User.User 
                return $ List.head . map fst . filter (\(_, user) -> user.username == m.sender) $ userTuples

replyToUserCid : Party -> T.TriggerA () (Optional (ContractId User.User))
replyToUserCid replyingParty = queryMessages 
            >>= filterForReceivedByReplier replyingParty
              >>= pickLastMessage 
                >>= checkIfReceivedByReplier replyingParty
                  >>= fromMessageToSenderUserCid

replyChoice : Party -> User.SendMessage
replyChoice replyingParty = User.SendMessage with 
  sender = replyingParty 
  content = "Please, tell me more about that."

sendReply : Party -> (Optional (ContractId User.User)) -> T.TriggerA () ()
sendReply replyingParty replyToUserCidOpt = do 
  case replyToUserCidOpt of 
    None -> return () 
    Some cid -> T.dedupExercise cid (replyChoice replyingParty) 

autoReplyRule : Party -> T.TriggerA () ()
autoReplyRule replyingParty = 
  replyToUserCid replyingParty
    >>= sendReply replyingParty 

-- THE SAME TRIGGER ASSEMBLED FROM BUILDING BLOCKS

autoReply' : T.Trigger ()
autoReply' = T.Trigger
  { initialize = pure ()
  , updateState = \_ -> pure ()
  , rule = autoReplyRule
  , registeredTemplates = T.AllInDar
  , heartbeat = None
  }

test' : Script ()
test' = do 
  alice <- allocateParty "Alice" 
  bob <- allocateParty "Bob" 

  aliceUserCid <- submit alice $ Daml.Script.createCmd User.User with username = alice, following = [bob]
  debug $ "Alide user cid: " <> show aliceUserCid
  bobUserCid <- submit bob $ Daml.Script.createCmd User.User with username = bob, following = [alice]
  debug $ "Bob user cid: " <> show bobUserCid

  bobMsgCid <- submit bob $ Daml.Script.exerciseCmd aliceUserCid User.SendMessage with sender = bob, content = "Hello Alice"
  
  let acsBuilder = toACS aliceUserCid <> toACS bobUserCid <> toACS bobMsgCid

  -- TESTING THE MODIFIED TRIGGER
  ((), cmds) <- testRule autoReply' alice [] acsBuilder Maps.empty ()

  debug $ length cmds
  
  assertExerciseCmd (flattenCommands cmds) $ \(cid, choiceArg) -> do
    cid === bobUserCid
    debug choiceArg
    choiceArg === User.SendMessage with
      sender = alice
      content = "Please, tell me more about that."

```

---

<div class="post-metadata">

### Author: ![nemanja](https://yyz2.discourse-cdn.com/flex034/user_avatar/forum.canton.network/nemanja/32/842_2.png) [@nemanja](https://forum.canton.network/u/nemanja)
#### Post date: [May 2, 2022, 4:52pm UTC](https://forum.canton.network/t/daml-triggers-composed-from-small-building-blocks/4460/2 "2022-05-02T16:52:57Z")

</div>

Hi @gyorgybalazsi 👋

What part specifically do you think could ebb done better?

---

<div class="post-metadata">

### Author: ![gyorgybalazsi](https://yyz2.discourse-cdn.com/flex034/user_avatar/forum.canton.network/gyorgybalazsi/32/684_2.png) [@gyorgybalazsi](https://forum.canton.network/u/gyorgybalazsi)
#### Post date: [May 3, 2022, 4:08pm UTC](https://forum.canton.network/t/daml-triggers-composed-from-small-building-blocks/4460/3 "2022-05-03T16:08:02Z")

</div>

Hi @nemanja thank you, maybe there is no obvious way to improve it, if you cannot see anything at first sight. I close this and will put some thinking into bringing back the use of `when` and `whenSome` instead of pattern matching which I avoided because it’s new to me.
