How to Instantiate template with optional parameters
Hi, how can I instantiate a template & pass in the optional parameters?
eg in following ShipmentReceived template has customsInfo and fdaInfo as optional, but I am getting an error while calling the create ShipmentReceived as I do not have them to pass as arguments (Will set those later in workflow etc)
controller order.purchaseOrderTerms.buyerAgent can
ReceiveShipment : ContractId ShipmentReceived
with
receiptText : Text
dateReceived : Date
do
create ShipmentReceived with receiptText, dateReceived, shippingInfo, order
-- ShipmentReceived
template ShipmentReceived
with
order : Order
shippingInfo : ShippingInfo
receiptText : Text
dateReceived : Date
customsInfo : Optional CustomsInfo
fdaInfo : Optional FDAInfo
where
signatory order.purchaseOrderTerms.buyerAgent
controller order.purchaseOrderTerms.buyerAgent can
AddCustomsInfo : ContractId ShipmentReceived
with
customsInfo1 : CustomsInfo
do
create this with customsInfo = customsInfo1
AddFDAInfo : ContractId ShipmentReceived
Nevermind, Found solution is to use None
create ShipmentReceived with receiptText, dateReceived, shippingInfo, order, customsInfo = None, fdaInfo = None
Followup on this. Doesnt not look like the code is working. (ie. Optional field is NOT getting set)
Eg in the Scenario/init-script if I do
let
customsInfo1 = CustomsInfo with
reference = "CustomsInfoRef"
date = DA.Date.date 2021 Jan 01
name = "CustomsInfo"
shipmentReceived1 <- submit buyerAgent1 do exerciseCmd shipmentReceived1 AddCustomsInfo with customsInfo1=customsInfo1
shipmentReceived1 still shows customsInfo (None)
thoughts?
Can you show the full code sample you are using now?
Hope this helps…
----------------------------------------------------------
-- Shipment
----------------------------------------------------------
template ShipmentInitiated
with
order : Order
shippingInfo : ShippingInfo
where
signatory order.quote.quoteInfo.seller
controller order.purchaseOrderTerms.buyerAgent can
ReceiveShipment : ContractId ShipmentReceived
with
receiptText : Text
dateReceived : Date
do
create ShipmentReceived with receiptText, dateReceived, shippingInfo, order, customsInfo = None, fdaInfo = None
----------------------------------------------------------
-- ShipmentReceived
----------------------------------------------------------
template ShipmentReceived
with
order : Order
shippingInfo : ShippingInfo
receiptText : Text
dateReceived : Date
customsInfo : Optional CustomsInfo
fdaInfo : Optional FDAInfo
where
signatory order.purchaseOrderTerms.buyerAgent
controller order.purchaseOrderTerms.buyerAgent can
AddCustomsInfo : ContractId ShipmentReceived
with
customsInfoNew : CustomsInfo
do
let
this.customsInfo = customsInfoNew
create ShipmentReceived with ..
AddFDAInfo : ContractId ShipmentReceived
with
fdaInfoNew :FDAInfo
do
let
this.fdaInfo = fdaInfoNew
create ShipmentReceived with ..
ClearShipment : ContractId ShipmentCleared
with
reference : Text
dateCleared : Date
do
create ShipmentCleared with shipmentReceived=this, dateCleared, buyer = order.quote.quoteInfo.buyer, buyerAgent = order.purchaseOrderTerms.buyerAgent
setup : Script ()
buyerAgent1 <- allocatePartyWithHint "BuyerAgent1" $ PartyIdHint with partyIdHint = "BuyerAgent1"
buyerAgentInvite1 <-submit buyer1 do exerciseCmd buyerRole1 InviteBuyerAgent with buyerAgent = buyerAgent1
buyerAgentRole1 <- submit buyerAgent1 do exerciseCmd buyerAgentInvite1 AcceptBuyerAgentInvitation
let
purchaseOrderTerms1 = PurchaseOrderTerms with
buyerAgent = buyerAgent1
-- Buyer accepts Quote from Seller 2
purchaseOrder1 <- submit buyer1 do exerciseCmd quote2 AcceptQuote with purchaseOrderTerms = purchaseOrderTerms1
--Seller accepts PO and Order is created
order1 <- submit seller2 do exerciseCmd purchaseOrder1 AcceptPurchaseOrder with acceptText = "Accepted"
-- Seller can InitiateShipment
let
shippingInfo1 = ShippingInfo with
shippingDate = DA.Date.date 2021 Jan 01
trackingNumber = "Text"
shipmentInitiated1 <- submit seller2 do exerciseCmd order1 InitiateShipment with shippingInfo = shippingInfo1
-- BuyersAgent can ReceiveShipment
shipmentReceived1 <- submit buyerAgent1 do exerciseCmd shipmentInitiated1 ReceiveShipment with receiptText = "Received", dateReceived = DA.Date.date 2021 Jan 01
-- Add CustomsInfo
let
customsInfo1 = CustomsInfo with
reference = "CustomsInfoRef"
date = DA.Date.date 2021 Jan 01
name = "CustomsInfo"
shipmentReceived1 <- submit buyerAgent1 do exerciseCmd shipmentReceived1 AddCustomsInfo with customsInfoNew=customsInfo1
-- Add FDA Info
let
fdaInfo1 = FDAInfo with
reference = "FDAInfoRef"
date = DA.Date.date 2021 Jan 01
name = "FDAInfo"
shipmentReceived1 <- submit buyerAgent1 do exerciseCmd shipmentReceived1 AddFDAInfo with fdaInfoNew=fdaInfo1Let’s start with a very simplified version of your example to illustrate the problem:
module Main where
import Daml.Script
data CustomsInfo = CustomsInfo
deriving (Eq, Show)
template ShipmentReceived
with
p : Party
customsInfo : Optional CustomsInfo
where
signatory p
controller p can
AddCustomsInfo : ContractId ShipmentReceived
with
customsInfoNew : CustomsInfo
do
let
this.customsInfo = customsInfoNew
create ShipmentReceived with ..
setup : Script ()
setup = do
p <- allocatePartyWithHint "P" $ PartyIdHint with partyIdHint = "P"
cid <- submit p (createCmd ShipmentReceived with p = p, customsInfo = None)
shipments <- query @ShipmentReceived p
debug shipments
cid <- submit p (exerciseCmd cid (AddCustomsInfo CustomsInfo))
shipments <- query @ShipmentReceived p
debug shipments
pure ()
As you said, this doesn’t work. We get the following output when running daml script:
[DA.Internal.Prelude:540]: [(<contract-id>,ShipmentReceived {p = 'P', customsInfo = None})]
[DA.Internal.Prelude:540]: [(<contract-id>,ShipmentReceived {p = 'P', customsInfo = None})]
Now what is going wrong here?
The crucial point is the following piece of code
let
this.customsInfo = customsInfoNew
create ShipmentReceived with ..
If you remove a bit of syntactic sugar this is equivalent to
let
this.customsInfo = customsInfoNew
create ShipmentReceived with p = p, customsInfo = customsInfo
Now the crucial questions is what does the customsInfo at the very end refer to.
To understand that we have to understand what the following line does.
let this.customsInfo = customsInfoNew
Remember that DAML is an immutable language. You cannot simply mutate a field in a template. This line isn’t mutating the customsInfo field so that customsInfo changes its meaning. customsInfo at the end still refers to the old value which is why you see the unchanged value.
To fix your code you have to actually change the value, e.g.,
createCmd ShipmentReceived with p = p, customsInfo = Some customsInfoNew
or if you want to use a let and .. you can use the following
-- Note that this does not mutate customsInfo. It defines a new variable of this name that hides the one from your template.
let customsInfo = Some customsInfoNew
createCmd ShipmentReceived with ..
If you now rebuild and rerun your script you will see the expected output:
[DA.Internal.Prelude:540]: [(<contract-id>,ShipmentReceived {p = 'P', customsInfo = None})]
[DA.Internal.Prelude:540]: [(<contract-id>,ShipmentReceived {p = 'P', customsInfo = Some CustomsInfo})]
Now there is one remaining piece of the puzzle. What does let this.customsInfo = customsInfoNew actually do? This is rather confusing: It defines an infix operator called . which accepts two arguments this and customsInfo and will return customsInfoNew. So you could call "hello" . "world" afterwards and it will give you back customsInfoNew.
Thank you for the detailed explanation. (Code works as expected now
)
createCmd ShipmentReceived with p = p, customsInfo = Some customsInfoNew
I have a variation of this problem
template SurgicalEvent
with
operator : Party
hospital : Party
emr: Text
patientname: Text
patientdob : Date
eventdate : Date
eventdescription : Text
surgeonname : Text
surgicaleventproductdata : Optional [SurgicalEventProductdata]
where
signatory operator
observer hospital
controller operator can
nonconsuming AddSurgicalEventData : ContractId SurgicalEvent
with
newproductdata : SurgicalEventProductdata
do
archive self
create this with surgicaleventproductdata = newproductdata :: surgicaleventproductdata
I tried to introduce “Some”
newproductdata : Some SurgicalEventProductdata
But errors occur
Some is not a type constructor. The type you are looking for is Optional SurgicalEventProductData which has the values None and Some productData for productData : SurgicalEventProductData.
If I have this
template SurgicalEvent
with
operator : Party
hospital : Party
emr: Text
patientname: Text
patientdob : Date
eventdate : Date
eventdescription : Text
surgeonname : Text
surgicaleventproductdata : Optional [SurgicalEventProductdata]
where
signatory operator
observer hospital
controller operator can
nonconsuming AddSurgicalEventData : ContractId SurgicalEvent
with
newproductdata: Optional SurgicalEventProductdata
do
archive self
create this with surgicaleventproductdata = newproductdata :: surgicaleventproductdata
get this error code in last line
C:\Users\bartc\Documents\Github\Loci\daml\SurgicalEvent.daml:30:9: error:
• Couldn't match type ‘Optional [SurgicalEventProductdata]’
with ‘[Optional SurgicalEventProductdata]’
arising from a functional dependency between:
constraint ‘DA.Internal.Record.HasField
"surgicaleventproductdata"
SurgicalEvent
[Optional SurgicalEventProductdata]’
arising from a use of ‘DA.Internal.Record.setField’
instance ‘DA.Internal.Record.HasField
"surgicaleventproductdata"
SurgicalEvent
(Optional [SurgicalEventProductdata])’
at <no location info>
• In the first argument of ‘create’, namely
‘(DA.Internal.Record.setField
@"surgicaleventproductdata"
newproductdata :: surgicaleventproductdata
this)’
In a stmt of a 'do' block:
create
(DA.Internal.Record.setField
@"surgicaleventproductdata"
newproductdata :: surgicaleventproductdata
this)
In the expression:
do archive self
create
(DA.Internal.Record.setField
@"surgicaleventproductdata"
newproductdata :: surgicaleventproductdata
this)typecheck
The issue is that you have an Optional [a] and you’re trying to add a Optional a to the front which doesn’t typecheck since Optional [a] is not a list. There are various options depending on your requirements. One solution would be to just change the type to [Optional a] which should make your example typecheck. Another would be to change it to [a] and do nothing if a None gets passed into AddSurgicalEventData. If you don’t want to change the types you have to define what you want to happen in the case where either the new or the old value is None.
Here is a solution for the second option:
template SurgicalEvent
with
operator : Party
hospital : Party
surgicaleventproductdata : [SurgicalEventProductdata]
where
signatory operator
observer hospital
controller operator can
nonconsuming AddSurgicalEventData : ContractId SurgicalEvent
with
optnewproductdata: Optional SurgicalEventProductdata
do
archive self
case optnewproductdata of
None -> pure self
Some productdata -> create this with surgicaleventproductdata = productdata :: surgicaleventproductdata
Side note: Please use code blocks surrounded by triple backticks ``` instead of quote blocks where each line starts with >. The latter strips all indentation which makes code examples very hard to read.
Such as:
```
my code here
```
productdata :: surgicaleventproductdata
Here is some updated template code where I keep the 2 data elements the same type
template SurgicalEvent
with
operator : Party
hospital : Party
emr: Text
patientname: Text
patientdob : Date
eventdate : Date
eventdescription : Text
surgeonname : Text
surgicaleventproductdata : Optional [SurgicalEventProductdata]
where
signatory operator
observer hospital
controller operator can
nonconsuming AddSurgicalEventData : ContractId SurgicalEvent
with
newproductdata : Optional [SurgicalEventProductdata]
do
archive self
case surgicaleventproductdata of
None -> create this with surgicaleventproductdata = newproductdata
Some surgicaleventproductdata ->
case newproductdata of
None -> create this
Some newproductdata -> create this with surgicaleventproductdata = newproductdata :: surgicaleventproductdata
This results in the following error on the last line…
C:\Users\bartc\Documents\Github\Loci\daml\SurgicalEvent.daml:39:39: error:
• Couldn't match type ‘Optional [SurgicalEventProductdata]’
with ‘[[SurgicalEventProductdata]]’
arising from a functional dependency between:
constraint ‘DA.Internal.Record.HasField
"surgicaleventproductdata"
SurgicalEvent
[[SurgicalEventProductdata]]’
arising from a use of ‘DA.Internal.Record.setField’
instance ‘DA.Internal.Record.HasField
"surgicaleventproductdata"
SurgicalEvent
(Optional [SurgicalEventProductdata])’
at <no location info>
• In the first argument of ‘create’, namely
‘(DA.Internal.Record.setField
@"surgicaleventproductdata"
newproductdata :: surgicaleventproductdata
this)’
In the expression:
create
(DA.Internal.Record.setField
@"surgicaleventproductdata"
newproductdata :: surgicaleventproductdata
this)
In a case alternative:
Some newproductdata
-> create
(DA.Internal.Record.setField
@"surgicaleventproductdata"
newproductdata :: surgicaleventproductdata
this)typecheck
Peek Problem (Alt+F8)
No quick fixes available
:: prepends a single element to a list. If you want to concatenate 2 lists, you need to use ++. However, in your example, you don’t have 2 lists. You have 2 Optional [a]. That gives you 4 cases to considner:
| surgicaleventproductdata | newproductdata | result |
|---|---|---|
| Some(xs) | Some(ys) | Some(xs ++ ys) |
| None | Some(ys) | What do you want here? |
| Some(xs) | None | What do you want here? |
| None | None | What do you want here? |
A common solution is to choose None as the answer for the last 3. You can get that as follows:
liftA2 (++) surgicaleventproductdata newproductdata
However, looking at your example I expect you might have something else in mind?
It is also useful to check if there is really a difference between None and Some []. If not, there is no need to use Optional at all here.
Elaborating on that last sentence, i.e. not using Optional at all and representing “no surgical data” as an empty list, you would write it as:
module Main where
data SurgicalEventProductData = SurgicalEventProductData
deriving (Eq, Show)
template SurgicalEvent
with
operator : Party
hospital : Party
emr: Text
patientname: Text
patientdob : Date
eventdate : Date
eventdescription : Text
surgeonname : Text
surgicaleventproductdata : [SurgicalEventProductData]
where
signatory operator
observer hospital
controller operator can
nonconsuming AddSurgicalEventData : ContractId SurgicalEvent
with
newproductdata: SurgicalEventProductData
do
archive self
create this with surgicaleventproductdata = newproductdata :: surgicaleventproductdata
This typechecks, and could be what you want depending on the broader context.