Repository navigation
Suggestion: Sum Types and Tagged Unions without Reflection #9241
Description
Activity
- changed the title
[-]Suggestion: More on Sum Types and Tagged Unions[/-][+]Suggestion: Sum Types and Tagged Unions without Reflection[/+]on Jun 18, 2016 - addedSuggestionAn idea for TypeScriptAn idea for TypeScriptNeeds ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.This issue needs a plan that clarifies the finer details of how it could be implemented.
on Jun 18, 2016 Just as an update, I have been reflecting on this problem some more.
It turns out that there are some problems with this technique that give me some pause.
The problem is that this can introduce a lot of 'boilerplate'
For example, lets say we used the above as the Node/AST for TypeScript. In order to traverse these trees, we would need to write code. Ditto for serialization and deserialization.
This is already happening in the TypeScript compiler with: ts.forEachChild. The function is 371 lines long.
In the Haskell,OCaml, and Scala communities, the discussion about 'Boilerplate' has been going on for quite some time and continues to this day. This may be a really complicated problem!
Few remarks:
- No to
case Type: .... That visually conflicts with existingswitchstatements, and the semantics would depend on the types in a very opaque way. (Currently, I know of no statement type whose semantics depend on the type of any part of it.) - I like the idea of a special
matchstatement, and think that should take the place of what you initially considered with theswitch. - Nominal typing of that kind of thing may depend on Support some non-structural (nominal) type matching #202.
- No to
drudru (@drudru) Oh, and although I'm neutral, expect some resistance from others.
Hi - one more update.
Some interesting things have happened since I opened this issue.
- C# 7.0 has shipped with pattern matching.
- TypeScript 2.0 RC has shipped with Tagged Unions.
Given the above, I think compatibility will be important. Also, I think the larger community will gain experience and some new solutions will be proposed.
I think it makes sense at this point to close this issue. Thanks for reading.
I'll also mention that a limited form of pattern matching also exists with destructuring. It's not exactly conditional like true pattern matching, but it's a step in that direction.
(And tagged unions in 2.0 RC do technically check for completeness with the unreachable code path analysis, building on the 1.8 type narrowing. Things like this would result in a compiler warning:)
// Switch statement function getValue(ref: One | Two | Three): number { switch (ref.type) { case "one": return 1 case "two": return ref.number case "three": return 3 default: return 4 // unreachable } } // If-else function doSomething(ref: One | Two | Three) { if (ref.type === "three") { blah() } else { if (ref.type !== "two") return baz() if (ref.type === "one") { doSomethingElse() // unreachable } } } interface One { type: "one" } interface Two { type: "two"; value: number } interface Three { type: "three" }
- locked and limited conversation to collaborators
on Jun 19, 2018
I’m very happy to see #186 close with pull request: #9163. This is a big step forward.
I read both of those and read Wes Wigham's recent work. I openned this as a new issue to avoid cluttering the closed issue.
Tagged Union without Reflection
I still think there is room to add some tagged-unions in the style of an FP language to TypeScript. The benefits are widely understood and documented. The key attribute of this suggestion is the removal of visibility into the tag of the union. This is how every other statically-typed FP language implements them since they also erase types at run-time.
Hopefully this can become part of the language at some point in the future. Considering the recent code landing in 2.0, the urgency is greatly reduced, but still worth discussing.
A quick list of benefits:
Proposed implementation:
Or an enum like type without associated values.
If ‘data’ is controversial as a keyword, then ‘datatype’ could be used.
I chose 'data' since the keyword 'type' is a type-synonym declaration in both Haskell and TypeScript.
Since Haskell uses 'data' to define new data types, I thought it best to keep that similarity.
Once these declarations are done, they are closed. So you see the entirety of the definition in a single place.
In terms of implementation, the Shape declaration would create a new type or class called Shape. Square, Circle, and Rectangle would be the subclasses. This is a common strategy for supporting the feature in OO languages. It was most notably used in Scala.
The Shape type above could be equivalent to.
... or they could be defined as functions that construct that type.
The type guards would switch purely on the value vs. the ‘kind’.
No run-time reflection should be used.
Although destructuring is part of ES6, and it can work on a class instance, it uses structural typing.
If two objects have the same shape, the developer may want the guarantee of nominal matching for destructuring. This proposal would make that simple.
That is what I have so far. It might be interesting to consider pretty-printing or serialization, but I think the overall object system may determine the preferred implementation.
As a side note, I was thinking very hard about #186 2 weeks ago. I wanted to have a proposal for something this Monday. However, I was trying to unify all the possible cases (enums, string types, classes, and a new variant type). I couldn't come up with a clean solution.
When the recent code landed from Anders on Monday, it was great news. Wwe going to have something we can use very soon in 2.0. It also simplified the problem this suggestion was originally addressing.