Are there opaque type examples to help me understand the practical use of it a bit better? The tutorial introduces example like NonEmptyList
(internally it's List a
). But then it loses all the List capabilities (e.g. List.map
can't work on this NonEmptyList
type). I thought this is similar to Haskell's newtype, but newtype can derive from typeclasses.
You would have to write wrapping functions for all of the list functions that you want to expose for that to work. That or a way to convert it back to a regular list to run the functions. (or make a generic function that unwraps and then runs the list funciton passed in)
I think the two most common reasons for opaque types are to:
An simple-ish example of hiding implementation detail is the Set
type in Roc: https://github.com/roc-lang/roc/blob/4952f5e1d03826b97f57668e938740225a0013fa/crates/compiler/builtins/roc/Set.roc#L36-L49
A set is simply a dictionary with no value.
It is exposes methods that are generally just convenience wrappers around dict
For 2, nonempty list is probably a prime example. It would probably be implemented as something like:
NonEmptyList a := {
head: a,
rest: List a,
}
Given it is non empty, you would always be able to access the head without checking, but other elements would still need checks.
For 3, the examples include thing like a username. You want to distingish the Username
type from Str
, but fundamentally a username is just a string.
We have some good first issues like #86 in the examples repo to write more examples for things like this. It would be super helpful if anyone is interested in making another example to show How to make and use Opaque types. :smiley:
Brendan Hansknecht said:
It is exposes methods that are generally just convenience wrappers around dict
seems very cumbersome to redirect all the APIs :( I totally agree the 3 principles/goals. But the actual practice requires a lot of user work.
Brendan Hansknecht said:
For 2, nonempty list is probably a prime example. It would probably be implemented as something like:
NonEmptyList a := { head: a, rest: List a, }
Then i need to write all the getters and setters. Can't even use pattern matching here. In practice, I may just do an alias...
NonEmptyList a = {
head: a,
rest: List a,
}
Depending on the use case, opaque types may not be the right choice.
I think they are much more likely to be found in libraries and in certian special small forms for security and better typing, like the username example
Also, a better example for 2, probably is something that has real verification. NonEmptyList
in it's regular type signiture actually guarantees everything. So the opaque type is pretty useless.
Something with verification that can't be done by the type system here probably makes more sense... maybe Url
or Email
which both have specific formats were any string is not valid.
I guess if the opaque type can derive the "abilities" from the implementation type, it would reduce boilerplate code. i can choose to expose certain abilities from my underlying implementation
oh wait, it can. i need to read the abilities doc
so I can create a Functor
ability that defines a fmap
function. Implement fmap
for List
. then I can write for NonEmptyList
that derives Functor
: list |> fmap fn
. Create my own Haskell :joy:
abilities aren't higher-kinded, so you couldn't make that specific ability :big_smile:
Last updated: Jul 05 2025 at 12:14 UTC