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.