gren-lang / gren-lang/compiler
Tagged Values
Nobody has claimed this yet.
- Dominant language
- Haskell
- Stars
- 503
- Forks
- 29
- PR merge metrics
- No merged PRs in 30d
Description
Before Gren 0.1 was released, tuples were removed from the language. This decision was based on "tuple fatigue" that I'd built up over time in both hobby- and work-related Elm projects. Tuples are not necessarily a bad language feature (I think they're perfect for the purpose of pattern matching over multiple values, or even returning multiple values from a function), but since they are so easy to create they tend to end up all over the place. Since tuples are, by nature, brittle data structures they in turn tend to make your program more brittle than necessary, which again complicates refactoring and prototyping.
Ultimately, while convenient, I considered tuples a feature that overall provide negative value to the language.
The reason I bring this up is because the same criticisms can be applied to custom types.
While custom types core feature, being able to represent a choice or a "one of these" situation, they have several shortcommings:
- Custom types cannot be inferred. They must be declared prior to use. The compiler cannot parse a case-of expression and infer the type involved.
- Data associated to a particular constructor has positional semantics, which is brittle in the same way that tuples are brittle. That is, adding or removing a value from a constructor breaks your program in every pattern match where said constructor is involved, even if the particular value is of little interest.
- Custom types cannot be extended, like records can.
In addition, custom types is being used in order to define opaque types, which is a powerful feature that should be able to stand on its own.
Instead, I believe custom types should be replaced by what I'm going to call Tagged Values.
Tagged Values
Tags are simple labels. At a glance, they look to be constructors, but they are structural as opposed to nominal.
It might be easier with an example:
type alias Maybe a = <Just a | Nothing>
This is the tagged value representation of the Maybe type. Note that this is a type alias. Tagged values are structural, not nominal.
This means that the following functions has the same type:
map1 : Maybe a -> (a -> b) -> Maybe b
map2 : <Just a | Nothing> -> (a -> b) -> <Just b | Nothing>
Tags can have exactly one associated value. No more, no less. Since it's sometimes useful to represent a tag without an associated value (enums, or the Nothing tag in the examples above) a value-less tag is syntax sugar for a tag associated with the empty record.
This is beneficial from a performance perspective, as it means that all tags can be represented using the same JavaScript type, meaning tags are monomorphic from the JS JIT's point of view. This is also beneficial from a maintanance standpoint, as you have to use records to associate multiple values to a tag, which allows you to add or (potentially) remove values without breaking the program.
Tags are also extensible. The following syntax is allowed:
type alias Tipe a = < a | Ctor1 | Ctor2 >
Tipe in the above example, can represent any union of tagged values, as long as they have a Ctor1 and Ctor2 tag. Such types can be represented in case-of expressions as long as there is a catch-all pattern.
Opaque types
In order to support opaque types, this issue would allow type aliases to be opaque.
module Opaque exposing
( Opaque
, NonOpaque (..)
)
type alias Opaque = Int
type alias NonOpaque = Int
As an extra benefit, there is a tiny bit less syntax for dealing with opaque types within a module, as you don't need to wrap them in a single-constructor custom type.
New syntax for defining aliases
With custom types removed, there is no need to differantiate between type and type alias.
So defining an alias can be shortened to
alias Opaque = Int
Or, as Roc does it:
Opaque : Int
Update 2026-05-19
From experience comes wisdom. I hope.
I want to amend this proposal in a few ways.
Tag names are fully qualified
Just defined in a module called Maybe is different from Just defined in a module called MyMaybe. The module name is an inherent part of a tag's name.
This makes it much less likely that you will get tag collisions when defining extensible unions.
Syntax changes
The proposal initially proposed the following syntax:
alias Maybe a = < Just a | Nothing >
Instead, the syntax will probably become:
type Maybe a = | Just a | Nothing
The leading | means that there's no difference between the first and second tags when spread over multiple lines.
Dedicated syntax for extending/merging unions
In the original proposal you could define an extensible tag using:
alias MyUnion a = < a | One | Two Int >
In this updated proposal we suggest that this will be changed to:
type MyUnion a = | ...a | One | Two Int
This makes it easier to differantiate between what is a tag, and what is another union to be merged when using concrete types
type MyUnion = | ...SomeOtherUnion | One | Two Int
Since tag names are now qualified, the above is legal even if SomeOtherUnion defines One or Two as part of it's definition, unless MyUnion and SomeOtherUnion is defined in the same module.
Type narrowing in when-is expressions
The type checker should be enhanced to narrow the possible variants inside the branches of when-is expressions.
import SomeOther
type MyUnion =
| ...SomeOther.Union
| One
| Two Int
eval : MyUnion -> Int
eval union =
when union is
One -> 1
Two num -> num
_ ->
-- compiler now knows that `union` == SomeOther.Union
SomeOther.eval union
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
The issue names no implementation files, tests, or compiler entry points. Start by reviewing the tagged-value, alias, opaque-type, and type-narrowing requirements in the proposal, then identify the parser and type-checker areas they affect. Done would require an agreed design and implementation plan for the proposed language changes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- haskell
- Domain
- compilers
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 20/100