roc icon indicating copy to clipboard operation
roc copied to clipboard

First-class opaque types (replacing private tags)

Open rtfeldman opened this issue 4 years ago • 2 comments

Background

Opaque types are a valuable programming language feature.

Currently, Roc implements them using private tags, e.g. @Foo instead of Foo means "a Foo tag, except scoped to the current module." Private tags were added to the language because back in 2018 I was trying to think of a lightweight design for how to support opaque types in Roc, and this was what I came up with at the time.

I think the following is a better design.

Design

Instead of private tags, the idea is to make opaque types a first-class concept in the language.

For example, let's say I want to make an opaque type called UserId, which is an U64 under the hood, but I don't want other modules to be able to access its behind-the-scenes representation. Here's how I might do that with this proposed design:

UserId := U64

You can read this as "UserId is an opaque wrapper around U64."

Within the scope where I wrote the := (it would work in expressions as well as at the top level, just like type aliases do), I can wrap and unwrap the opaque UserId type by writing @UserId. (Note that this is the same as the current private tag syntax, except you're putting the @ in front of the name of the type itself. In this world, private tags would no longer exist.) So for example:

fromU64 : U64 -> UserId
fromU64 = \number -> @UserId number

toStr : UserId -> Str
toStr = \@UserId number -> Num.toStr number

If I tried to write @UserId outside the scope where the UserId := was defined, I'd get an error. That type is opaque outside the scope where it was defined!

That's basically the whole design. Of note, type inference should work with these, so I could do UserId := _ and the compiler should be able to figure out the wrapped type from context.

Benefits

This design has a few benefits.

  • It makes Abilities possible. (The Abilities design couldn't be implemented on top of private tags.)
  • It should make the language easier to learn completely, because opaque types are a concept that should be taught regardless, and now they can be taught in the context of a feature that directly implements their purpose.
  • It will speed up type-checking, because opaque types can have a more efficient representation than tag unions do.

Implementation

  • [x] Modify the parser such that UserId := U64 parses to a new Def AST node called OpaqueType. (Like type aliases, this should be supported as a top-level Declaration within a module as well as a def inside an individual expression.)
  • [x] To avoid having trunk be in a broken state, make the syntax be $UserId for now instead of @UserId, so we can make progress without breaking private tags. (Then at the end we can remove private tags and swap it from $ to @.) Parsing $UserId in both expressions and patterns (to be Expr::OpaqueType and Pattern::OapqueType) would be a prerequisite for this.
  • [x] Update canonicalization to use Symbols for the OpaqueType Def as well as Expr::OpaqueType and Pattern::OpaqueType, and give naming errors if they're not in scope. (We probably want this to work similarly to how we resolve naming errors for
  • [x] Add a check to canonicalization for if you're using @Foo without Foo := having been defined in the current scope. (Note that this is not the same as the Foo type being in scope! If it were the same, then these types wouldn't actually be opaque; I would be able to use @Foo even if I'd just imported the Foo type from another module.)
  • [x] Incorporate opaque types into type constraint generation. (We shouldn't need to change solving for this; Apply already does what we want.)
  • [x] Add a way to expose top-level opaque types (just the type) to other modules, and for other modules to import them.
  • [x] Convert all tests that currently use private tags over to use opaque types.
  • [x] Remove private tags from the language.
  • [x] Change the $ in the parser (and tests) to @
  • [ ] #2220
  • [ ] Add support for instantiating opaque types to the editor.
  • [ ] Add support for pattern matching on opaque types to the editor.

rtfeldman avatar Dec 15 '21 04:12 rtfeldman

Is this all done now except for the editor parts?

jared-cone avatar Apr 30 '22 05:04 jared-cone

yep!

ayazhafiz avatar Aug 10 '22 22:08 ayazhafiz