Shortly after joining DA, I was fortunate to have the opportunity of working on a large client project making heavy use of Daml. I’d like to share one of the software engineering approaches I learnt, which I think every budding Damler runs into sooner or later: handling namespaces.
In an object-oriented language like Java, class methods will have namespaces assigned automatically. For instance:
class Cat {
boolean fur;
}
In the above snippte fur
inherits the Cat
namespace, so you can use a fully qualified name like so: Cat#fur
. Not so in Daml or Haskell.
You’ve probably run into a problem like this at some point:
module Main where
template MarriageProposal
with
proposer: Party
proposee: Party
where
signatory proposer
observer proposee
controller proposee can Accept: Marriage do undefined
template Marriage
...
template BusinessProposal
with
proposer: Party
proposee: Party
where
signatory proposer
observer proposee
controller proposee can Accept: Business do undefined -- parse error!
template Business
...
This module fails to compile. The reason is that there are two definitions of Accept
, which live in the same Main
namespace. A typical workaround for this is to prefix the definition with it’s templates name e.g. MarriageAccept
and BusinessAccept
to avoid the clash.
This still leaves another, similar problem: When importing these templates into another module, the fields proposer
and proposee
are again clashing. Prefixing everything can lead to really messy code.
The way to solve this properly is through the use of qualified imports. Split your Main
module into two files: Marriage.daml
and Business.daml
, structured like this:
module Marriage where
type T = Marriage
template Proposal
with
proposer: Party
proposee: Party
where
signatory proposer
observer proposee
controller proposee can Accept: Marriage do undefined
template Marriage
-- ...
Do the same for Business
.
You can now import these into a third file explicitly, like so:
import qualified Marriage (Proposal, Accept, T)
import qualified Business (Proposal, Accept, T)
getMarried : Script Marriage.T
getMarried = do
cid <- submit bob $ createCmd $ Marriage.Proposal bob alice
submit alice $ exerciseCmd cid Marriage.Accept
getRich : Script Business.T
getRich = do
cid <- submit alice $ createCmd $ Business.Proposal alice charlie
submit charlie $ exerciseCmd cid Business.Accept
You’ll notice the use of the alias T
to avoid having to write e.g. Marriage.Marrige
in type definitions.
It can get a bit cumbersome if you want to use a constructor; in this case, I tend to just add a second import for the unqualified constructor name e.g. import Marriage (Marriage)
so that I don’t need to write Marriage.Marriage with ...
This approach lets you use both modules by specifying their namespace explicitly, and as you can see is very similar to the syntax granted by OO languages. In cases where there’s no ambiguity, you can just drop the qualified
modifier.
It also plays surprisingly well with generated code outside the ledger e.g. typescript bindings.
To close, I’ll suggest that if you find this useful, you should also learn about explicit export lists (i.e. module Main (Marriage, Business) where ...
instead of just module Main where ...
). They let you emulate public/private
member access of OO languages: just omit private functions from the export list.
Hope this was helpful. Comments welcome!