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?
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
because adding a type annotation could change what ability implementations it had
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:
Foo := ...
to type Foo = Foo ...
you don't need to repeat the Foo
, but otherwise it's basically saying the same thinggetBar = \Foo { bar } -> bar
and getBar = \@Foo { bar } -> bar
the difference is just the @
, which we need to distinguish between tags and opaque wrappers (because \Foo { bar } ->
always means a tag in Roc, not an opaque type wrapper)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.
that's a good question...I haven't really thought about it, to be honest!
Last updated: Jul 06 2025 at 12:14 UTC