Trigger and Script code reuse

This is somehow related to other question:

In my case:

  1. I have a function with a signature like:
    myFuncA: Party -> MyTemplate -> Script (Optional (Commands ()))

which I use in daml test - the Idea is that returned command is later executed with submit or submitMustFail

  1. I have also a trigger that uses (inside rule) a function
    myFuncB : Party -> MyTemplate -> TriggerA (Optional CommandId) (Optional (Command))

The point is that myFuncA and myFuncB have almost identical code (text of code - types are different) . Small Difference is that script uses queries like
sth <- query @XX party
and trigger version
sth <- query @XX

So far I could not reuse code.

Changing above function to a form:
myFuncB : Action m => Party -> MyTemplate -> m (Optional (Command))
leads to compilation errors like:

 Could not deduce (ActionTriggerAny m)
  arising from a use of ‘query’

Any suggestions, ideas to try?

If you have a truly nontrivial amount of code you want to reuse this way, then perhaps you can consider typeclasses.

Start your research with the official documentation and my longer version on the forum. At this point, you should have a strong, clear grasp on why Action m => alone yields the error in your experiment above.

With that as a prerequisite:

It’s worth considering query from Daml.Trigger: it is defined in terms of the ActionTriggerAny typeclass so that the commonalities between initialize, updateState, and rule could share an API.

Instead of supplying ActionTriggerAny to unify parts of these three kinds of Action, we could have supplied three different query functions. In that case, it would still be possible for the user of the library to unify them with a typeclass like ActionTriggerAny.

Now, if it is possible to unify any part of two action APIs, why is it not done by the libraries themselves? Two reasons:

  1. Structural: the libraries are considered independent; it is a major constraint to require them to conform to a common interface. Ironically, the unification I mentioned above would have been made more difficult or impossible were trigger committed previously to a separate interface.
  2. Semantics: as @cocreature mentioned previously, superficial similarity can belie essential differences. query might look very similar, but that masks important differences in the way it interacts with command submission in the Script action API vs the TriggerA action API. Such differences can “poison” downstream code, seeming innocent in small test cases until they finally deliver unexpected results in the worst circumstances.

So, what does it mean when we put any possible unification in the hands of you, the trigger/script writer? It means that you are responsible for understanding how those semantic differences may affect your triggers’ and scripts’ behavior. However, once you accept that, you can express whatever the typeclass system lets you express, including, possibly, the unifying constraint you are looking for with respect to your triggers and scripts.

If that is the path you wish to follow, the starting point I suggest–once you have a strong handle on the constraint docs I linked above–is to inspect how class ActionTriggerAny and its instances are implemented in Trigger.daml.