Soon we'll have support for Dictionaries that implement their behavior via value hashing, and in order for their API to be generic, they'll need a way to say that they accept keys that support both the Hash and Eq ability. Right now we have ways to say that a type variable supports one ability, but not multiple. Here are a few ideas for what a syntax for that would look like:
Dict k v := <internal dict repr> | k has Hash + Eqfoo : a, b -> c | a has Hash + Eq, b has Encode + Eq, c has DisplayDict k v := <internal dict repr> | k has Hash, Eqfoo : a, b -> c | a has Hash, Eq, b has Encode, Eq, c has DisplayDict k v := <internal dict repr> | k has Hash, k has Eq
foo : a, b -> c
| a has Hash, a has Eq,
b has Encode, b has Eq,
c has Display
Personally I find the "Plus" variety the most readable. Thoughts? Any other ideas?
I also don't think the "Individual clauses" variety is too bad, but I think it would get much more noisy if we rename a has Hash to a supports Hash - in that case
a supports Hash + Eq
feels much more readable than
a supports Hash, a supports Eq
even before more type variables are involved
I agree around noise and plus being the most readable of these options.
I'm not advocating for it, but we should consider &
Dict k v := <internal dict repr> | k has Hash & Eqfoo : a, b -> c | a has Hash & Eq, b has Encode & Eq, c has DisplayMathematically, plus seems the best to me
Linguistically, ampersand seems the best to me
I noticed that in Json.roc, something Json has encode and decode. That is written like a has [Hash, Eq]. I would assume we want symmetry between has for requiring an ability and has for defining a type implements an ability?
When I read a has [Hash, Eq] it looks similar to Tags and so it feels like an OR so Hash | Eq. If it is to be read an an AND then my preference is for the &, particularly noting the multi-variable clauses you have presented.
How about a has (Hash, Eq) in the vain of Haskell's typeclasses annotation?
Brendan Hansknecht said:
I noticed that in Json.roc, something Json has encode and decode. That is written like
a has [Hash, Eq]. I would assume we want symmetry betweenhasfor requiring an ability andhasfor defining a type implements an ability?
I do like the symmetry, but I know others have found it confusing - and we've talked about switching to a keyword like "supports" in these situations, so you would instead have e.g.
Hash has
hash : hasher, a -> hasher | hasher supports Hasher, a supports Hash
In that world I don't think k supports [Hash, Eq] would work too well, since we'd be conflating the "declaration" syntax with the "usage" syntax which folks could find confusing
I'm going to spike out the Hash & Eq idea. The syntax is the easiest part to change, so we can experiment.
The first pass implementation of the ampersand idea Hash & Eq is up at PR 4305. Here are some tests showing what that would look like, and error messages when you duplicate or forget to include certain abilities:
Is any form of this implemented yet?
PR above
Maybe I am missing something, but I only see the single file diff. Not immediately seeing a way to go to the pr from that.
it’s https://github.com/roc-lang/roc/pull/4305
thanks
From https://github.com/roc-lang/roc/pull/4305#discussion_r1003631445, per @Richard Feldman suggesting where as looking cleaner than | with ,:
With consistent prefix punctuation, this could look pretty clean with | and , with the following format:
f : {} -> a
| a has Eq & Hash & Decode
, b has Eq & Hash
(at least from my perspective, mixing prefix and suffix punctuation/tokens should be avoided)
Alternatively, it'd look pretty clean to have:
f : {} -> a
| a has Eq & Hash & Decode
| b has Eq & Hash
My one hesitation is that this is using & and | where & means 'and' but | doesn't mean 'or'.
What about something like this?
foo : a, b -> c
| a has Eq, Hash, Decode
| b has Eq, Hash
alternatives:
foo: a, b -> c
| a implements Eq, Hash, Decode
| b implements Eq, Hash
foo: a, b -> c
where
a implements Eq & Hash & Decode,
b implements Eq & Hash
also there's the question of how they look in the more common single case:
insert : Dict k v, k, v -> Dict k v
| k has Hash, Eq
insert : Dict k v, k, v -> Dict k v
| k implements Hash, Eq
insert : Dict k v, k, v -> Dict k v
where k implements Hash, Eq
I think it's easier to guess what | does when it's implements instead of has
So more like this? I think it feels clean. I prefer typing in the comma instead of the ampersand, and using the bar on a separate line is easy to read.
insert: Dict k v, k, v -> Dict k v
| k implements Eq, Hash, Decode
| v implements Eq, Hash
insert: Dict k v, k, v -> Dict k v
| a implements Eq, Hash, Decode
I'm not a fan of a | on each line. It feels like it would be some sort of nested where clause. Feels too separate to me. But I'm sure I'd get used to it eventually.
Would prefer just to have the next line tabbed in
something: x, y -> y
| x implements Eq
y implements Hash
Like this? also would this be where it lives, i.e. before the function implementation?
insert: Dict k v, k, v -> Dict k v
| k implements Eq, Hash, Decode
| v implements Eq, Hash
insert = \dict, key, value ->
# code here
# etc
insert: Dict k v, k, v -> Dict k v
| a implements Eq & Hash & Decode
insert = \dict, key, value ->
# code here
# etc
insert: Dict k v, k, v -> Dict k v
k implements Eq, Hash, Decode
v implements Eq, Hash
insert = \dict, key, value ->
# code here
# etc
insert: Dict k v, k, v -> Dict k v
a implements Eq & Hash & Decode
insert = \dict, key, value ->
# code here
# etc
something we haven't discussed:
insert: Dict k v, k, v -> Dict k v
& k implements Eq, Hash, Decode
& v implements Eq, Hash
insert = \dict, key, value ->
Brendan Hansknecht said:
something: x, y -> y | x implements Eq y implements Hash
That would suggest it could also read (with poorer formatting) as:
something: x, y -> y
| x implements Eq y implements Hash
Is that something we'd want to permit in the grammar, or would the spacing/line-endings be significant?
In the inlined form above, I might read that as "x implements (Eq y) which in turn implements Hash"
Richard Feldman said:
foo: a, b -> c where a implements Eq & Hash & Decode, b implements Eq & Hash
Perhaps also:
foo: a, b -> c
where a implements Eq, Hash, Decode
where b implements Eq, Hash
(treat where as introducing a constraint on a single type, rather than a clause of arbitrarily many constraints)
, feels to me like a more intuitive separator of traits which a type "implements" (since we're listing out the traits, and lists are naturally separated by a comma, at least in English).
& would seem appropriate to me if this were expressed not as traits possessed, but rather intrinsic properties... For example:
foo: a, b -> c
where a is Eq & Hash & Decode
where b is Eq & Hash
If we wanted to limit additional keywords, perhaps we could do something like:
foo: a, b -> c
:: a -> Eq & Hash & Decode
:: b -> Eq & Hash
:: could mean "sub-declaration", since it's not used for lists, and -> could arguably be consistent with the parameter declaration syntax if we take it to mean "evaluates to" (e.g. "foo evaluates to type c when applied, and b evaluates to a type which is both an Eq and a Hash when unified [during type unification]").
My personal favorite:
foo: a, b -> c
where
a implements Eq, Hash, Decode
b implements Eq, Hash
@Anton 's favorite looks super clean and easy to understand but is Roc white space sensitive in other places?
So more like this? Does it make any difference with the implementation there as well?
insert: Dict k v, k, v -> Dict k v
where
k implements Eq, Hash, Decode
v implements Eq, Hash
insert = \dict, key, value ->
# code here
# etc
@Georges Boris Yes, Roc is whitespace sensitive in a small number of places, similar to Elm and Haskell. I think local variable definitions are an example.
I also prefer where. Sometimes I feel we are overdoing it with punctuation!
where is also my favorite
I do wonder though if much is lost if we instead of
where
k implements Eq + Hash + Decode,
v implements Eq + Hash,
write
where
k : Eq + Hash + Decode,
v : Eq + Hash,
The : already means "has type", which is a correct intuition here I think (though it is of course different in a technical sense). the : key is actual punctuation, which should mean it's easy to reach (unlike say |, at least on my keyboard), and a good deal shorter than the implements word
Good idea
Anton said:
My personal favorite:
foo: a, b -> c where a implements Eq, Hash, Decode b implements Eq, Hash
this idea doesn't necessarily require whitespace sensitivity, for what it's worth - since abilities don't have type parameters, Hash, Decode b implements already has enough information to tell where the break happens because b is lowercase, there wasn't a comma after Decode, and there was an implements after b
(although, having said that, I would want the formatter to immediately turn this into how Anton wrote it :laughing:)
I'd say this one is my personal favorite too
a concern I have about overloading : is that when you look at them in isolation, they look exactly like types - e.g. k : Hash is something you could write at the top level of any Roc program and have it mean something totally different than if you wrote it in the context of another type annotation :sweat_smile:
one of the things I like about having a word instead of punctuation (e.g. has, supports, implements) for the token that appears before the abilities is that it reinforces that these are not about classification
Maybe it's the color-free syntax of the examples here but "implements" when reading fast seems another ability in the list, having some kind of symbol separating can help visually, even if it's not :
e.g. I'm explicitly opposed to using is there even though it's concise, because it encourages treating abilities as classifications, which I think is a trap - I think it's way more fun than actually useful to spend a bunch of time thinking about how to classify different types, and one of my big concerns with adding abilities to the language in the first place is that a bunch of userspace classifications would pop up in the ecosystem
I like that k implements Eq, Hash is very clear that "all this is saying is that k is responsible for implementing the logic for Eq and Hash"
it's syntactically about as far as you can get from "Dog and Cat both inherit from Animal" - which somehow sounds reasonable when you say it to a beginner (because it's logically true) - whereas like "Dog and Cat both implement Animal" makes me think "...what does that even mean?!" which is, I think, the correct response.
Last updated: Jun 16 2026 at 16:19 UTC