First off, and I can’t stress this enough: mentioning explicit party names in code is a very bad code smell in Daml. There are very few cases where that is the right approach.
Ignoring that, as @cocreature said, if you don’t want your code to stop you have to return something. The simplest option is to wrap your return value in an Optional
and return None
for items you’d like to skip.
But first, let’s place your original snippet in a context where it actually compiles:
module Main where
import Daml.Script
template ContractA
with
sig: Party
id: Text
abc: Text
obs: Party
where
signatory sig
observer obs
key (sig, id) : (Party, Text)
maintainer key._1
template ContractB
with
sig: Party
collabData: [(Party, Text)]
where
signatory sig
data SRData = SRData with a: Text
deriving Show
setup : Script ()
setup = script do
party1 <- allocatePartyWithHint "DV1" (PartyIdHint "DV1")
party2 <- allocatePartyWithHint "DV2" (PartyIdHint "DV2")
mainParty <- allocatePartyWithHint "mainParty" (PartyIdHint "mainParty")
submit party1 do
createCmd ContractA with sig = party1, obs = mainParty, id = "keyData", abc = "some data"
submit party2 do
createCmd ContractA with sig = party2, obs = mainParty, id = "keyData", abc = "some other data"
collect <- submitMulti [mainParty, party1, party2] [] do
createCmd DoCollection with main = mainParty, others = [party1, party2]
submit mainParty do
exerciseCmd collect CreateContractB with parties = [party1, party2], id = "keyData"
return ()
template DoCollection
with
main: Party
others: [Party]
where
signatory main :: others
nonconsuming choice CreateContractB : ()
with parties: [Party]
id: Text
controller main
do
dvdata <- forA parties (\dv -> do
cID <- lookupByKey @ContractA (dv, id)
case cID of
Some cId -> do
dvCon <- fetch cId
case partyToText dv of
"DV1" -> do
let sRDV1 = SRData with a = dvCon.abc
return (sRDV1)
"DV2" -> do
let sRDV2 = SRData with a = dvCon.abc
return (sRDV2)
_ -> error "No valid DV"
-- Note: can't return `()` here because that's the
-- wrong type
None -> error "original code errors here")
debug dvdata
This prints:
[SRData {a = "some data"},SRData {a = "some other data"}]
And the (inferred) type of dvdata
is [SRData]
.
I’ve removed the def
part out of pure laziness; the main change is that the None
case cannot return ()
, because there is no type that includes both ()
and the return values of the Some
branch (SRData
).
This is, in fact, the same problem you have with the _
case.
Wrapping your return value in an Optional
solves both:
do
dvdata <- forA parties (\dv -> do
cID <- lookupByKey @ContractA (dv, id)
case cID of
Some cId -> do
dvCon <- fetch cId
case partyToText dv of
"DV1" -> do
let sRDV1 = SRData with a = dvCon.abc
return (Some sRDV1)
"DV2" -> do
let sRDV2 = SRData with a = dvCon.abc
return (Some sRDV2)
_ -> return None
None -> return None)
debug dvdata
which prints
[Some (SRData {a = "some data"}),Some (SRData {a = "some other data"})]
where the inferred type of dvdata
is now [Optional SRData]
.
So what actually happens if we’re missing some data? You can try it; for example, removing the creation of party1
's contract, we now print:
[None,Some (SRData {a = "some other data"})]
Similarly, adding a third party and creating a contract for them would yield:
[Some (SRData {a = "some data"}),Some (SRData {a = "some other data"}),None]
And, if we both don’t create the contract for "DV1"
and add a third party, we’d print:
[None,Some (SRData {a = "some other data"}),None]
One issue with this is that we don’t know why things fail. In this last list, the first None
is because the contract was not found, whereas the second None
was because the contract was found but did not have the properties we wanted (wrong party in this case). Rather than returning a Optional
, we could choose to return a Either
. Whereas Optional a
has two constructors Some a
and None
, where only Some a
can have attached data, Either a b
has two constructors Left a
and Right b
, both of which can have attached data (of potentially different types a
and b
).
Using this, we could return some explanation for the error:
do
dvdata <- forA parties (\dv -> do
cID <- lookupByKey @ContractA (dv, id)
case cID of
Some cId -> do
dvCon <- fetch cId
case partyToText dv of
"DV1" -> do
let sRDV1 = SRData with a = dvCon.abc
return (Right sRDV1)
"DV2" -> do
let sRDV2 = SRData with a = dvCon.abc
return (Right sRDV2)
_ -> return (Left "wrong party")
None -> return (Left "contract not found"))
debug dvdata
which prints:
[Left "contract not found",Right (SRData {a = "some other data"}),Left "wrong party"]
now of type [Either Text SRData]
.
Finally, maybe you just don’t care about the errors and you just want a list of the contracts that do match. You could write a filtering function yourself:
filterEither : [Either a b] -> [b]
filterEither [] = []
filterEither (e::es) = case e of
Left _ -> filterEither es
Right a -> a :: filterEither es
or, in this case, use the builtin DA.Either.rights
. For reference, here is the complete code again, this time using Either
as the return type, skipping the contract creation for "DV1"
, adding a "DV3"
, and printing both the complete list with failures and the list of only the successes:
module Main where
import Daml.Script
import qualified DA.Either
template ContractA
with
sig: Party
id: Text
abc: Text
obs: Party
where
signatory sig
observer obs
key (sig, id) : (Party, Text)
maintainer key._1
template ContractB
with
sig: Party
collabData: [(Party, Text)]
where
signatory sig
data SRData = SRData with a: Text
deriving Show
filterEither : [Either a b] -> [b]
filterEither [] = []
filterEither (e::es) = case e of
Left _ -> filterEither es
Right a -> a :: filterEither es
setup : Script ()
setup = script do
party1 <- allocatePartyWithHint "DV1" (PartyIdHint "DV1")
party2 <- allocatePartyWithHint "DV2" (PartyIdHint "DV2")
party3 <- allocatePartyWithHint "DV3" (PartyIdHint "DV3")
mainParty <- allocatePartyWithHint "mainParty" (PartyIdHint "mainParty")
--submit party1 do
-- createCmd ContractA with sig = party1, obs = mainParty, id = "keyData", abc = "some data"
submit party2 do
createCmd ContractA with sig = party2, obs = mainParty, id = "keyData", abc = "some other data"
submit party3 do
createCmd ContractA with sig = party3, obs = mainParty, id = "keyData", abc = "yet another data"
let others = [party1, party2, party3]
collect <- submitMulti (mainParty :: others) [] do
createCmd DoCollection with main = mainParty, ..
submit mainParty do
exerciseCmd collect CreateContractB with parties = others, id = "keyData"
return ()
template DoCollection
with
main: Party
others: [Party]
where
signatory main :: others
nonconsuming choice CreateContractB : ()
with parties: [Party]
id: Text
controller main
do
dvdata <- forA parties (\dv -> do
cID <- lookupByKey @ContractA (dv, id)
case cID of
Some cId -> do
dvCon <- fetch cId
case partyToText dv of
"DV1" -> do
let sRDV1 = SRData with a = dvCon.abc
return (Right sRDV1)
"DV2" -> do
let sRDV2 = SRData with a = dvCon.abc
return (Right sRDV2)
_ -> return (Left "wrong party")
None -> return (Left "contract not found"))
debug dvdata
debug (filterEither dvdata)
debug (DA.Either.rights dvdata)