proposal-enum
proposal-enum copied to clipboard
Supports for ADT enum?
The enums in Swift, Rust and Scala (case class) can have values and it can be unwrapped by pattern matching.
// swift
enum Command {
jump
run(velocity: Int)
punch(target: Enemy, power: Int)
}
let command = Command.run(20)
switch command {
case .jump:
print("jump")
case .run(let velocity):
print("run(velocity: \(velocity))")
case .punch(let target, let power):
print("run(target: \(target), power: \(power))")
}
It would be more useful to support Algebraic Data Types (ADT), so I would like to include it in this proposal.
enum Command {
jump
run(velocity)
punch(target, power)
}
// Stage 1 Pattern Matching
const command = Command.run(20);
case (command) {
when Command.jump -> {
console.log(`jump`);
}
when Command.run(velocity) -> {
console.log(`run(velocity: ${velocity})`);
}
when Command.punch(target, power) -> {
console.log(`punch(target: ${target}, power: ${power})`);
}
}
// types
typeof Command.run === "function";
typeof Command.run(20) === "object";
// not necessary if implementation becomes difficult
Command.run(20) === Command.run(20);
FYI: https://github.com/rustwasm/wasm-bindgen/issues/31
wasm-bindgen does not yet support enums with payload, because JS itself doesn't do so.
@petamoriken I noticed that the Prior Art section of proposal-enum doesn't mention enums like the ones you mention in Rust, Swift, and Scala. (or the ML-family language features that inspired the Rust/Swift/Scala enums)
Even if the proposal doesn't grow to allow enums with payloads, it would be helpful for the proposal to mention them and say why they aren't included.
@rbuckton Any thoughts on this?
Given that newer languages such as Rust, Swift, etc are all generalising the concept of enums into rich ADTs, it seems strange to base a modern JS proposal on "traditional" enums...
I can think of numerous benefits of an ADT-oriented approach over a "traditional" one, particularly in the functional programming space. Some off the top of my head:
- Easy functional utility types
enum Result { Failure(value), Success(value), } enum Optional { None, Some(value), }
- ADT-oriented domain modelling:
enum ContentType { HTML(charSet), JSON(charSet), FormData(boundary), } enum ContentDisposition { Inline, Attachment(fileName, fileNameStar), FormData(fieldName, fileName, fileNameStar), }
- Opportunities for first-class integration with pattern matching (including potential exhaustiveness checks using something like TypeScript)
I'm not sure I'd personally support the introduction of "traditional" enums into JS, as I feel they're too primitive and don't add enough value to justify the extra syntax. In contrast, I'd be hugely in favour of introducing more modern, ADT-oriented enums to the language, as they're a more flexible construct that provides vastly superior modelling capabilities. 🙂
I'm curious about what the benefits of JS enum-with-payload are as compared to the following pattern I see a lot in TypeScript:
type Result<Success, Failure> = {
kind: "success",
value: Success
} | {
kind: "failure",
value: Failure,
};
function handleResponseOrThrow<S, F>(result: Result<S, F>): S {
switch(result.kind) {
case "success":
// value is of type Success here
return result.value;
case "failure":
// value is of type Failure here
throw Error(`failed ${result.value}`);
default:
// next line will error at compile time if there is an unhandled enum variant
// (something other than "success" or "failure")
const _exhaustivenessCheck: never = result;
// really should be unreachable
throw Error("unreachable");
}
}
A JS solution could probably have more succinct pattern matching and exhaustiveness checking.
I do hope I have ADT enum at least once a week but I think TS won't do that cause JS doesn't have that. I'm strong desire to see ADT enum in JS.
One possibility of allowing enums with payloads would be to leverage some of the design around struct
in https://github.com/rbuckton/proposal-struct and allow only wasm-primitive and struct
-compatible types in the payload definitions:
enum Result {
Failure(i32, box), // `box` currently indicates any ECMAScript language value
Success(box)
}
const res = Result.Failure(404, "not found");
However, this needs a form of pattern matching to be truly usable.
Without pattern matching, it is still useful. It can simplify the creation of the tagged union.
For example
const f = () => Result.Success(123)
const x = f()
switch (x.kind) {
case Result.Success:
alert(x.items[0]) // 123
}
@rbuckton I was thinking we could leverage something similar to Scala's Extractor pattern (i.e. give enum values an unapply
method that returns a tuple containing the original inputs). That would enable extraction of values in the absence of pattern matching, and could hypothetically be worked into any pattern matching implementation.
One possibility of allowing enums with payloads would be to leverage some of the design around
struct
in https://github.com/rbuckton/proposal-struct and allow only wasm-primitive andstruct
-compatible types in the payload definitions:enum Result { Failure(i32, box), // `box` currently indicates any ECMAScript language value Success(box) } const res = Result.Failure(404, "not found");
Would Failure(1, box) === Failure(1, box)
evaluate to true
?
@rbuckton I was thinking we could leverage something similar to Scala's Extractor pattern (i.e. give enum values an
unapply
method that returns a tuple containing the original inputs). That would enable extraction of values in the absence of pattern matching, and could hypothetically be worked into any pattern matching implementation.
Ideally, there would be a custom way of extracting values, so that data structure authors can maintain their API boundaries.
unapply
is one way, but view patterns are more general (could be used with things that are not enums). So unapply
might not be needed if proposal-pattern-matching supports view patterns or there is a follow-on proposal for view patterns.
I wrote up a sketch for possible ADT support a few months ago, though the current struct
proposal doesn't quite align with that design anymore: https://gist.github.com/rbuckton/4a5108fab40ac90551bf82d9884711b5
I also just wrote up this as another example of how this could work using normal classes/functions: https://gist.github.com/rbuckton/192c2922650e05a1ca9cd7c01be7fc6c
@rbuckton I was thinking we could leverage something similar to Scala's Extractor pattern (i.e. give enum values an
unapply
method that returns a tuple containing the original inputs). That would enable extraction of values in the absence of pattern matching, and could hypothetically be worked into any pattern matching implementation.
Good news, https://github.com/tc39/proposal-extractors reached Stage 1 at the last TC39 meeting, so hopefully we will be able to build that up and build on it for ADT enums.