Daml Types ContractId and Party are represented as plain Strings vs why not types? (Java Bindings)

The primitives provided by Daml LF: How Daml Types are Translated to Daml-LF — Daml SDK 2.3.0 documentation

generally map to language equivalents. But ContractId and Party and mapped into regular String values instead of a wrapper such as a ContractId.class and Party.class. There is the Value.class implementations of Party and ContractId but these seem to be lower level representations used for grpc transformations.

Is there a design decision around using the String vs a specific class?

I ask because when working with the java-bindings a dev design decision that keeps coming up is understanding what the expected data type that is being sent and returned into/from the grpc channel:

Understanding the intended data format allows downstream interpretation of the data: For example, understanding that a Key is (Party, Party, String) vs (String, String, String), allows a generation validations (does the Party being created even exist before being sent into the ledger), allows type handling to provide better experiences (allow a key with Parties to have the UI generate lookups for the Display name of that party (without having to hard code the logic of the specific template into the UI). Same concepts for ContractId.

You can have a complex mix of keys as shown in: Input limit on template Key - #3 by cocreature. So when the bindings receive these keys, they are just a mix of “String, String, String, String”, and the bindings have ~no-way to understand them (yes we could parse the content for string patterns such as a party starting with the word party, but lots of weak points to deal with in that solution).

Additionally, It is easy to mix/typo your way into mixing contractId and party values (as they are both strings and use heavily in the same methods) (similar for workflowId, commandId, and transactionId, but much less common usage).

It looks like it would be valuable to wrap Party and ContractId into its own class wrappers just as the Value Interface has Party and ContractId values.

Some notes:

  1. generated Contract classes always wrap the contract id in the ContractId class, but when you receive the contract id it is in a String format:
public class TransactionTree {
    private final String transactionId;
...

CreatedEvent:
...
public @NonNull String getContractId() {
        return this.contractId;
    }
...

etc

Now we can say that we know it is the contractId in that case, but as soon as we move it out of that method and into another value, or list of objects we lose the typing on contract Id as it is regular String.

  1. The most noticeable loss of the typing is in the use of keys when you want the client to understand the structural content of the key and have to hard code it in based a reading of the Daml template.
1 Like

As an example of the applicability for Keys:

When you set keys you are defining type or a Record:

type OrganizationKey = (Party)
type SomeOtherKey = (Party, Party)
type SomeOtherOtherKey = (Party, Party)
type SomeThirdKeyType = (Party, Party)

In these examples, the name of the Key is lost when converted into bindings. We get keys such as “String, String”. So we lose the typing of the key’s content and we lose the contextual name of the key,

I can’t answer your main question since I don’t know the history here but for your last question, maybe consider defining record types instead of synonyms for tuples. IME once you have to define a synonym for a tuple you’re almost always better off defining a custom record, e.g.,

data SomeOtherKey = SomeOtherKey
  with
    firstParty : Party
    secondParty : Party
  deriving (Eq, Show)

Agreed! that’s exactly what i have done. But it feels like using tuples becomes an anti-pattern, but docs examples are “99%” tuple based.

Very good point, I think a lot of those examples should be updated.