Managing namespaces in Daml

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!

8 Likes

Another Haskell descendant, Elm uses a similar approach.

In Elm, you have eg a map function in the List package and in the String package.

The List version:

map : (a -> b) -> List a -> List b

The String version:

map : (Char -> Char) -> String -> String

If you want to use both functions in a file, it’s best to use them in a qualified way you have described as List.map and String.map.

3 Likes

Yes, that’s a good point. I also use qualified to avoid clashes for data structures such as Set.toList, Map.toList or the more general Foldable.toList. There are lots of these functions that are named the same.

2 Likes