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!

11 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

Hi @Luciano this is what I was searching for just now :+1:t2:

Based on others answers to order and formatting that I have seen & received, would I be correct in stating that the order of qualified imports is upto the application writer?

One of my application sections, has 7 templates, with very similar actions therefore I have learned the hard way, that the use of the same term like messageSender in multiple locations cause dissonance.

For the sake of Logic flow, would you/anyone tend to set the first discrete section/function as the prime document, and then import into there, or perhaps choose the most important section as the prime document?

Assuming I’ve understood you correctly here, do you mean:

import qualified Marriage (Proposal, Accept, T)
import qualified Business (Proposal, Accept, T)

vs

import qualified Business (Proposal, Accept, T)
import qualified Marriage (Proposal, Accept, T)

Yes, I do believe that there is no difference in which order you put these. One approach is to sort these alphabetically. I think somebody even wrote a tool for doing this to Daml programs … it’s linked somewhere in the forum.

Ah, my poor English, I meant:

File: JointVenture.daml

module JointVenture where

import qualified Marriage (Proposal, Accept, T)
import qualified Business (Proposal, Accept, T)

Or File: Marriage.daml

module Marriage where

import qualified JointVenture (Proposal, Accept, T)
import qualified Business (Proposal, Accept, T)

And so on …

Here I’m not entirely sure I understand what you’re asking.

Perhaps an example of how I structure my code helps:

When writing a (non-pure) daml program, I start by writing down business logic in a template, with choices left as undefined . The key thing here is that each choice has typically multiple controllers. This means that you won’t be able to exercise these through the API directly, as you won’t have multiple signatures (although you can test this in a script using submitMulti).

As a second step I write what I think of as “workflow” contracts in another package, which takes care of first collecting signatures and then calling these multi-party choices in the business contract. The workflows tend to be accept/propse style contracts typically, and that’s where I’ll have the name clashes, so they’re the ones I import qualified, leaving my core business logic as the ‘top level’ module. That’s the idea of the approach:

  • business logic stays in contracts with multiple-controller choices.
  • workflows are imported into the business module, and can be made up of one or more templates, with single controllers, to collect signatures. These can be called extrernally from the API.

I think this also answers your first question above.

1 Like

Agree, thank you very much.

I had not considered that method as you detailed,
it is quite elegant and logical.

I used this method to import the Doctor module into another Module, like this

module Doctor where

type T = Doctor -- Error message
...
module AnotherModule where

-- Import Doctor module for PrescriptAuth function
--
import qualified Doctor (CommenceConsult,
                         ContinueConsult,
                         CompleteConsult,
                         PrescriptAuth,
                         T)
...

From your example you import the Templates, which leads me to ask, do you have to import all of the Templates in that Module, or just the ones you need?

In my example I am getting an error message indication on the Doctor record type, Error message below:

/home/quid/Projects/daml/code/getmednz/daml/Doctor.daml:6:10: error:
    Not in scope: type constructor or class ‘Doctor’typecheck

Why?

Type synonyms reference a type but based on your example it looks like you haven’t defined a type called Doctor which is why you get the error. If you look at the example Luciano wrote you can see that the Marriage module also defines a Marriage template.

3 Likes

I’ll re-read & adjust, after I have a working UI :man_facepalming:t2:

When you write import qualified Marriage with no import list, you get all the [exported] symbols. Unlike normal import, where you’re merging all imported symbols into the unqualified namespace, there’s rarely a reason to list imports at all, since the origin of the symbol is already obvious according to each reference location. That is, because each reference looks like Marriage.Proposal in the importing file, we get no value from the fact that Proposal was listed with the import.

The exception to this is if you’re using the somewhat obscure feature of merging multiple qualified imports to a single qualifier:

import qualified Foo as F
import qualified Bar as F

This might call for an import list for the same reason that unqualified imports call for an import list: you’re merging multiple modules’ contents to a single qualifier.