-
Notifications
You must be signed in to change notification settings - Fork 12.8k
(suggestion) tagged union types #10253
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
This is a nice suggestion but needs a lot more explanation as to what exactly's being proposed before we can look at it. |
Well, they are also called algebraic data types. Using a combination of tagged OR types (union types) and tagged AND types (objects), you could implement a lambda calculus like this (not tested):
The |
@jeffersoncarpenter Have you taken a look at What's new in TypeScript - Tagged union types? They are very cool :) PS: with #9407 (already merged in master and available in |
Hmm, is it possible to narrow those types at the expression level rather than at the statement level (i.e. with a construct that's like my |
Yes, you can. I've rewritten type Var = { kind: 'var', name: string }
type Lambda = { kind: 'lambda', var: string, expr: Term }
type App = { kind: 'app', func: Term, val: Term }
type Term = Var | Lambda | App
function never(x: never): never {
throw new Error(`Not a never ${x}`);
}
function fail(reason?: string): never {
throw new Error(reason);
}
// Only expressions
let evaluateTermWithContext: (context: Object) => (term: Term) => Term =
context => _term => {
const term = _term;
return term.kind === 'var' ? context[term.name]
: term.kind === 'lambda' ? term
: term.kind === 'app' ?
((func: Term) => func.kind !== 'lambda'
? fail('lambda expected')
: evaluateTermWithContext(
Object.assign({}, context, { [func.var]: term.val })
)(func.expr)
)(evaluateTermWithContext(context)(term))
: never(term);
}
// More idiomatic and much more readable
function evaluateTermWithContext2(context: Object): (term: Term) => Term {
return term => {
switch (term.kind) {
case 'var': return context[term.name];
case 'lambda': return term;
case 'app': {
const func = evaluateTermWithContext2(context)(term);
if (func.kind !== 'lambda') {
throw new Error('lambda expected');
}
const newContext = Object.assign({}, context, {
[func.var]: term.val
});
return evaluateTermWithContext(newContext)(func.expr);
}
}
};
}
var evaluateTerm : (term : Term) => Term = evaluateTermWithContext({}); One interesting thing to note is that TypeScripts Discriminated unions are much more powerful than e.g. Haskell's ADTs - they can be discriminated on arbitrary fields, not only "constructor", can be freely extended through intersection types/other unions, or have their options reduced by checking for specific ones and factoring them out. As a nice bonus "constructors" (they really are a shape, not a function) are first class types, which is huge - you can make your functions accept or return just a specific option and it all works. |
If by "expression level" you meant the object literal, then there are two separate problems to tackle
It's hard to imagine the need for typing such an object literal though (except for faking ADT functionality, which is already built-in with a better machinery). On the other hand, having the upper two issues implemented will bring a lot of flexibility to what can be expressed. |
This is all pretty much doable today with existing syntax. |
Javascript objects generally represent tagged intersection types, for example
{a : Number, b : Number }
, the intersection of two numbers.Tagged union types can also be represented using Javascript objects. An instance of a tagged union type has one property, of the specified type. Values of a tagged union type can only be used in a program through case splitting. Here's a quick code example:
The text was updated successfully, but these errors were encountered: