Stream: beginners

Topic: opaque type examples?


view this post on Zulip Steven Chen (Dec 14 2023 at 23:10):

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.

view this post on Zulip Brendan Hansknecht (Dec 14 2023 at 23:19):

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)

view this post on Zulip Brendan Hansknecht (Dec 14 2023 at 23:19):

I think the two most common reasons for opaque types are to:

  1. hide implementation details
  2. claim that something has been verified to be in a specific format.
  3. ensuring the type is considered unique and not passed to the wrong function

view this post on Zulip Brendan Hansknecht (Dec 14 2023 at 23:21):

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.

view this post on Zulip Brendan Hansknecht (Dec 14 2023 at 23:21):

It is exposes methods that are generally just convenience wrappers around dict

view this post on Zulip Brendan Hansknecht (Dec 14 2023 at 23:23):

For 2, nonempty list is probably a prime example. It would probably be implemented as something like:

NonEmptyList a := {
    head: a,
    rest: List a,
}

view this post on Zulip Brendan Hansknecht (Dec 14 2023 at 23:24):

Given it is non empty, you would always be able to access the head without checking, but other elements would still need checks.

view this post on Zulip Brendan Hansknecht (Dec 14 2023 at 23:25):

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.

view this post on Zulip Luke Boswell (Dec 14 2023 at 23:33):

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:

view this post on Zulip Steven Chen (Dec 14 2023 at 23:36):

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.

view this post on Zulip Steven Chen (Dec 14 2023 at 23:37):

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,
}

view this post on Zulip Brendan Hansknecht (Dec 15 2023 at 00:04):

Depending on the use case, opaque types may not be the right choice.

view this post on Zulip Brendan Hansknecht (Dec 15 2023 at 00:04):

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

view this post on Zulip Brendan Hansknecht (Dec 15 2023 at 00:05):

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.

view this post on Zulip Brendan Hansknecht (Dec 15 2023 at 00:06):

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.

view this post on Zulip Steven Chen (Dec 15 2023 at 00:09):

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

view this post on Zulip Steven Chen (Dec 15 2023 at 00:11):

oh wait, it can. i need to read the abilities doc

view this post on Zulip Steven Chen (Dec 15 2023 at 01:03):

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:

view this post on Zulip Richard Feldman (Dec 15 2023 at 01:22):

abilities aren't higher-kinded, so you couldn't make that specific ability :big_smile:


Last updated: Jul 05 2025 at 12:14 UTC