Stream: beginners

Topic: Why do we need to wrap/unwrap opaque types using @-syntax?


view this post on Zulip Jasper Woudenberg (Mar 23 2024 at 07:50):

It's taking me a while to get an intuition for opaque types in Roc. I realize now that they're closer to type aliases than tags. I think part of the reason it's taking me some time to build intuition for opaque types in Roc is that I'm used to how Elm and Haskell do opaque unexposed type constructors.

I also think the syntax for constructing and deconstructing opaque types puts me on the wrong foot a bit: It makes it look like we're wrapping and unwrapping a value in a constructor, similar to how you do tags in Roc or constructors in Elm and Haskell. Also, while in Elm/Haskell there's a nice symmetry between how you define the type (constructors with other types as parameters) and how you use types (same constructors, but parameters are nov values), in Roc the definition of the type using :=, and the use of the type using @ is visually distinct.

I think opaque types would feel a lot more like aliases if you didn't need the @ syntax, like so:

Foo := { bar : U8 }

getBar : Foo -> U8
getBar = \{ bar: 4 } -> bar

Which made me wonder why it doesn't work this way. Is it to allow type inference to distinguish between values meant to represent the opaque type the type it "aliases"? Are the @.. "constructors" essentially type annotations?

view this post on Zulip Richard Feldman (Mar 23 2024 at 11:15):

pretty much - another way to think of it is that otherwise type annotations would be required to tell what the behavior of the value is

view this post on Zulip Richard Feldman (Mar 23 2024 at 11:16):

because adding a type annotation could change what ability implementations it had

view this post on Zulip Richard Feldman (Mar 23 2024 at 11:21):

comparing how Roc does it to Elm, opaque types in Roc are basically 1-constructor, 1-payload custom types where the constructor isn't allowed to be exposed, e.g.

Roc

Foo := { bar : U8 }

getBar : Foo -> U8
getBar = \@Foo { bar } -> bar

Elm

type Foo = Foo { bar : U8 }

getBar : Foo -> U8
getBar = \Foo { bar } -> bar

so the differences are:

view this post on Zulip Jasper Woudenberg (Mar 28 2024 at 22:41):

Thanks Richard! I guess that's the part what I was thinking about, thinking of opaque types as a wrapper.

Question: Do you use the wrapper analogy to explain opaque types to most audiences, or would you only do that with someone you know is familiar with Elm or Haskell?

The question underlying that previous question: I'm wondering if seeing opaque types as wrappers is helpful or not to understanding what they are / how to use them.

view this post on Zulip Richard Feldman (Mar 29 2024 at 01:22):

that's a good question...I haven't really thought about it, to be honest!


Last updated: Jul 06 2025 at 12:14 UTC