How to type cast one interface into another?

When we treat Reference as a keyed template to store contract id of a template which implements both Task (as a base) and ReviewTask, how to type cast stored contract id to interface ReviewTask and get
access to comments field or Review choice?

module Daml.Grants.Interface.Workflow.Tasks.Base where
....
....
-- | View for `Task`.
data View = View
  with
    taskKey : TaskKey
      -- ^ Unique identifier of the task.
    assignee : Party
      -- ^ Party responsible for that task.  
    description : Text
      -- ^ A human readable description of the task.
  deriving (Eq, Show)
...
...
-- | An interface to implement task
interface Task where
  viewtype V

  getKey : TaskKey

  nonconsuming choice GetView : View
    -- ^ Retrieves the interface view.
    with
      viewer : Party
        -- ^ The party retrieving the view.
    controller viewer
    do
      pure $ view this
...
...
template Reference
  with
    taskKey : TaskKey
      -- ^ Unique key of the task.
    cid : ContractId Task
      -- ^ The contract id of the task.
    observers : PartiesMap
  where
...
...
    nonconsuming choice GetCid : ContractId Task
      -- ^ Get the `Workflow Task`'s contract id.
...
...
    choice SetCid : ContractId R
      -- ^ Set the instrument cid. This choice should be called only from `Task` implementations.
      with
        newCid : ContractId Task
          -- The task cid.
...
...

module Daml.Grants.Interface.Workflow.Tasks.ReviewTask where
import Daml.Grants.Interface.Workflow.Tasks.Base qualified as BaseTask (I, Implementation)

-- | View for `ReviewTask`.
data View = View
  with
    comments : Text
  deriving (Eq, Show)

-- | An interface to implement tasks
interface ReviewTask where
  viewtype V

  asBaseTask : BaseTask.I
    -- ^ Conversion to `Base Task` interface.

  review' : Review -> Update (ContractId BaseTask.I)
    -- ^ Implementation of `Review` choice.

  choice Review : ContractId BaseTask.I
...
...

module Daml.Grants.Interface.Workflow.Tasks.Factory where
...
...
interface Factory where
  viewtype V

  create' : Create -> Update (ContractId Task.I)
    -- ^ Implementation of `Create` choice.
  fetchByKey' : FetchByKey -> Update (ContractId Task.I)
    -- ^ Implementation of `FetchByKey` choice.
  nonconsuming choice Create : ContractId Task.I
...
...
  nonconsuming choice FetchByKey : ContractId ReviewTask.I
...
...

module Daml.Grants.Workflow.Tasks.ReviewTask where
instance ReviewTask.HasImplementation T

-- | A representation of ReviewTask object
template ReviewTask
  with
    taskKey : TaskKey
      -- ^ The task's key.
...
...
    comments : Text
      -- ^ Review on the task.  
    observers : PartiesMap
      -- ^ Observers of the task.
  where
...
...

    interface instance BaseTask.I for ReviewTask where
      view = BaseTask.View with taskKey; assignee; description
      getKey = taskKey

    interface instance ReviewTask.I for ReviewTask where
      asBaseTask = toInterface @BaseTask.I this
      view = ReviewTask.View with comments
...
...

-- Testing of the above logic

-- Create an instance of task factory 
    taskCid <- toInterfaceContractId @Task.I <$> submit grantingAgency do
        exerciseCmd factoryCid Task.Create with
            id
            assignee = technicalReviewer
            owner = grantingAgency
            description = "Technical application review"
            observers = M.fromList [("TechnicalReviewer", S.singleton technicalReviewer)]

-- A party creates a new task 
    let k = TaskKey with owner = grantingAgency; id
    taskFetchedCid <- toInterfaceContractId @Task.I <$> submit grantingAgency do
        exerciseCmd factoryCid Task.FetchByKey with
            taskKey = k

-- Fetch newly created task's view by the key  
    viewTask <- submit grantingAgency do
        exerciseCmd taskFetchedCid Task.GetView with
            viewer = grantingAgency

-- Couldn't fetch   
    submit technicalReviewer do
        exerciseCmd taskFetchedCid ReviewTask.Review with 
            comments = "Technical details are missing"

Hi @code_monkey,

My suggestion would be to try and work as much as you can with the “derived” interface ReviewTask.I.

This is because this interface offers you a way to cast to the “base” interface Task by using the asBaseTask method.

When this is not possible, you can use coerceContractId to cast in the opposite direction (from Task.I to ReviewTask.I). However, this will throw an error when the input contract does not implement ReviewTask.I.

Matteo

My go to reference is always daml-finance lib. Whatever I found over there, I try to replicate over in my project (based on my understanding and judgement). I have seen this pattern in daml-finance lib to cast the contract Id to base instrument and reference template keyed this value … not the derived one.

I had suspicioned fetchInterfaceByKey that it must be doing it somehow but couldn’t grasp the concept of coerceContractId. I couldn’t find any documentation on coerceContractId. If possible, can you please explain its syntax little more.

-- | Fetch an interface by key.
fetchInterfaceByKey : forall t k i. (HasFetchByKey t k, HasField "cid" t (ContractId i), HasFetch i) => k -> Update i
fetchInterfaceByKey k = do
  d <- snd <$> fetchByKey @t k
  fetch $ coerceContractId d.cid

Also

When this is not possible, you can use coerceContractId to cast in the opposite direction (from Task.I to ReviewTask.I).

Does it mean we have to fetch it again? Where does it specify, coerceContractId will turn a particular derived interface? coerceContractId @ReviewTask.I d.cid not coerceContractId @Disclosure.I d.cid

Hi,

coerceContractId is indeed currently poorly documented. This is due to interfaces still being an early access feature under active development. You can expect this to change once interfaces are no longer considered experimental.

In order to exercise ReviewTask.Review on the contract with cid taskFetchedCid, you can do the following

let reviewTaskCid : ContractId ReviewTask.I = coerceContractId taskFetchedCid -- this casts the Task.I ContractId to a ReviewTask.I ContractId
submit technicalReviewer do
  exerciseCmd reviewTaskCid ReviewTask.Review with 
            comments = "Technical details are missing"

I hope this clarifies the syntax of coerceContractId.

Sometimes, the compiler is able to automatically infer the type you are coercing to, as in the case of the fetchInterfaceByKey helper function you posted above.

1 Like