Record as a Key

Hi,

Sorry for the basic question, but I can’t find it in the doc … if I have a data type that i need to be in a Set , is there (like for a template) a way to indicate what attributes compose a Key?

data MyElem = MyElem with 
   foo: Text
   bar: Text
   something: Int
  somethingelse: Party
deriving (Eq, Show)

How can I have Set MyElem

Sets (DA.Next.Set) are implemented as Maps. Set MyElem is just Map MyElem (). DAML currently only has native maps with text keys (type TextMap. So the Map and Set implementations in DA.Next.Set and DA.Next.Map require one to write serialization and deserialization code to Text.

You can use show as serialization, but then have to write parsing code, which is non-trivial.

This is clearly awkward so we have “Generic Maps” and thus also Generic Sets on our roadmap. You can already test Generic Maps as follows:

Set your project to build DAML-LF 1.dev (which will eventually become 1.9) by adding this to your daml.yaml:

build-options:
  - --target=1.dev

Then import DA.Map in your project, add Ord to the list of typeclasses derived by MyElem and treat Map MyElem () as your set. The DA.Set wrapper library doesn’t exist yet, but it would be an easy adaption from daml/Set.daml at a080b47e347277e099167fc59affde9dac313342 · digital-asset/daml · GitHub.

2 Likes

I feel the need to point out that any dar produced using --target=1.dev is very much not supported for any kind of production use. Even just uploading it to a persistent ledger is likely to break things. This is really just meant for dev; please only upload such dars to throwaway ledgers.

While there is a point at which we will create a DAML LF 1.9, it would certainly not be safe to assume 1.9 will be exactly what 1.dev is today, nor a strict superset of it.

1 Like

So, are you saying that the ordering determines the keys used for hashing, in answer to OP? I expect I’ve misunderstood this, but if not, isn’t this a pretty hefty constraint? Is it not enough to have some sort of a Hashable typeclass?

Would this mean that in OP’s example, you’d need to write an instance Ord that then picks which particular field to order on - i.e. using deriving Ord in this particular case wouldn’t work?

Btw. is Ord a total order?

1 Like

I’m not in the loop on the specific work for the new DA.Map, but I think I can offer some elements of an answer nonetheless.

First off, yes, the Ord typeclass is intended to represent a total order. deriving Ord, when it works, does that.

Regarding the requirement for Ord, while one could argue it is not, strictly speaking, a requirement to store something in an associative data structure, and a Hashable typeclass could do the trick if the underlying implementation were indeed a hash table, it’s also possible to implement maps (and sets) using binary trees based on ordering the given elements, as is the case for Data.Set in Haskell. Based on @bernhard’s stated requirement to add Ord, I suspect the current prototype for DA.Map is based on similar techniques.

2 Likes

@Jean_Safar

Something like this is what you’re looking for

import DA.Text
-- We need a separator character that is not in either of our text fields
-- nor a digit
-- nor a Party (https://github.com/digital-asset/daml/blob/ba74146c10d149687d7f42972a9ced6203c3f807/daml-lf/spec/daml-lf-1.rst#literals)
sep : Text
sep = ";"

instance M.MapKey MyElem where

  keyToText c = c.foo
      <> sep <> c.bar
      <> sep <> M.keyToText c.something
      <> sep <> M.keyToText c.somethingelse

  keyFromText t = case splitOn sep t of
                    [ foo, bar, somethingT, somethingelseT] ->
                      MyElem with
                        foo
                        bar
                        something = M.keyFromText somethingT
                        somethingelse = M.keyFromText somethingelseT
                    _ -> error $ "Malformed key" <> t
2 Likes

Note that this "; can never be used in a string ever" is likely to bite you in the future. If adding an Ord instance does not work for you, and you really need to support translation to and from a text format, I’d recommend implementing a simple string-based replacement à la htmlencode: on the way in, replace ; with &semicolon and & with &amp, and do the opposite on the way out. Then you know there is no ; in the encoded strings and you can safely use that as a delimiter.

2 Likes

+1 to Gary’s suggestion!

1 Like

Let me add some clarification around the upcoming generic map type:

Together with the generic Map type, DAML-LF has gained a builtin generic comparison function. This works for roughly anything that is serializable (not quite accurate but a decent approximation). For incomparable values, e.g., functions, this throws a runtime error.

For DAML, we want to prevent invalid values statically. Currently, this is accomplished by requiring an Ord instance. However, the Ord instance will not actually be used by the map. Instead it will use DAML-LF’s builtin notion of equality. Since nothing stops you from defining an Ord instance for a record containing a function, you can technically still produce something that crashes.

Having two notions of ordering is rather confusing so there is an open question here if it would be better to forbid user-defined Ord instances to make sure that Ord instances always match up with the builtin notion of equality which is also much faster.