Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upTypeScripts Type System is Turing Complete #14833
Comments
This comment has been minimized.
This comment has been minimized.
|
Just a pedantic tip, we might need to implement a minimal language to prove TypeScript is turing complete. http://stackoverflow.com/questions/449014/what-are-practical-guidelines-for-evaluating-a-languages-turing-completeness hmmm, it seems this cannot prove turing completeness. |
HerringtonDarkholme
referenced this issue
Mar 24, 2017
Closed
stack overflow in recursive type alias + lookup type #14837
This comment has been minimized.
This comment has been minimized.
|
That's pretty interesting. Have you looked into using this recursion so as to say iterate over an array for the purpose of e.g. doing a type-level The idea of doing array iteration using type-level recursion is raising a few questions which I'm not sure how to handle at the type level yet, e.g.:
|
This comment has been minimized.
This comment has been minimized.
|
So, TS can prove False? (as in Curry-Howard) |
This comment has been minimized.
This comment has been minimized.
hediet
commented
Mar 24, 2017
|
I think stacks a typed length and with each item having an individual type should be possible by adding an additional type parameter and field to the numbers from my example above and storing the item in the number. Two stacks are half the way to proving formal turing completeness, the missing half is to implement a finite automata on top of that. |
RyanCavanaugh
added
the
Discussion
label
Mar 24, 2017
This comment has been minimized.
This comment has been minimized.
hediet
commented
Mar 25, 2017
|
@be5invis What do you mean with that? |
This comment has been minimized.
This comment has been minimized.
|
@hediet: Yeah, good point that in the absence of a way to infer type-level tuple length, we might get around that by manually supplying it. I suppose that'd also answer the destructuring question, as essentially you'd just keep picking out I suppose that still leaves another question to actually pull off the array iteration though. It's coming down to the traditional The remaining question for me would be how to deal with the iteration check, whether as It feels like you've covered exactly these kind of binary checks in your Disclaimer: my understanding of the general mechanisms here may not be as far yet. |
tycho01
referenced this issue
Mar 28, 2017
Closed
Proposal: add a built-in `reduce` function on the type level #12512
This comment has been minimized.
This comment has been minimized.
|
So I think where this would get more interesting is if we could do operations on regular type-level values (e.g. type-level Results:
These puzzles probably won't find solutions anytime soon, but if anything, this does seem like one thread where others might have better insights... |
This was referenced May 29, 2017
This comment has been minimized.
This comment has been minimized.
|
I made some progress, having tried to adapt the arithmetic operators laid out in the OP so as to work with number literals instead of special types. Skipped prime number stuff, but did add those operators like I mainly wanted to use them for that array iteration/manipulation though. Iteration works, and array manipulation, well, we can 'concatenate' tuple types by constructing a numeric object representing the result (with I'm honestly amazed we got this far with so few operators. I dunno much about Turing completeness, but I guess functions seem like the next frontier now. |
This comment has been minimized.
This comment has been minimized.
aij
commented
Aug 2, 2017
|
@be5invis You're thinking of an unsound type system. Turing completeness merely makes type checking undecidable. So, you can't prove false, but you can write something that is impossible to prove or disprove. |
This comment has been minimized.
This comment has been minimized.
johanatan
commented
Aug 5, 2017
isiahmeadows
referenced this issue
Aug 8, 2017
Open
Allow object types to have property-like associated types #17588
This comment has been minimized.
This comment has been minimized.
iamandrewluca
commented
Aug 10, 2017
•
|
This is like c++ template metaprogramming ? |
This comment has been minimized.
This comment has been minimized.
VermillionAzure
commented
Aug 28, 2017
|
@iamandrewluca From what I understand -- yes. |
This comment has been minimized.
This comment has been minimized.
VermillionAzure
commented
Aug 28, 2017
•
Possible relevant tickets: I'm just wondering if this would affect how recursive type definitions are currently handled by TypeScript. |
This comment has been minimized.
This comment has been minimized.
aleksey-bykov
commented
Dec 8, 2017
|
so we can we write an undecidable type in ts, cant we? |
mlhaufe
referenced this issue
Mar 9, 2018
Open
Allow classes to be parametric in other parametric classes #1213
This comment has been minimized.
This comment has been minimized.
pauldraper
commented
Mar 22, 2018
•
Assuming TypeScript type system is indeed Turning complete, yes. For any TS compiler, there would be a program that the compiler could not correctly type check. Whether this matters practically is an entirely different story. There are already programs you could not compile, due to limited stack space, memory, etc. on your computer. |
This comment has been minimized.
This comment has been minimized.
If you try to take idiomatic functional programming concepts from JS to TS (generics, currying, composition, point-free function application), type inference breaks down pretty much immediately. The run-time JS though runs fine. Hardware isn't the bottleneck there. |
This comment has been minimized.
This comment has been minimized.
This is true regardless of whether the type system itself is Turing-complete. function dec(n: number): boolean {
return n === 0 ? true : dec(n - 1);
}
let x: boolean = dec(10) ? true : 42;TypeScript can't typecheck this program, even though it doesn't evaluate to an error. Turing Completeness in the type-system just means type-checking now returns yes/no/loop, but this can be dealt with by bounding the type-checker (which I think already happens). @tycho01 checking !== inferring (Though I agree with your point). |
This comment has been minimized.
This comment has been minimized.
paldepind
commented
Apr 14, 2018
What do you mean? The TS compiler checks that program just fine and properly finds the type-error? |
This comment has been minimized.
This comment has been minimized.
johanatan
commented
Apr 14, 2018
|
@paldepind The point is that the else clause of the ternary is unreachable so the program should in fact pass the type checker (but it does not); i.e., dec(10) returns true (and 10 is a compile time constant/literal). |
This comment has been minimized.
This comment has been minimized.
paldepind
commented
Apr 18, 2018
|
@johanatan Just because the else clause is never taken doesn't make the program well-typed. Generally how code is actually run during execution does not affect whether or not a program is well-typed. If you look at the specification it states that the type of a conditional expression is the union of the types of the two expressions (i.e. the if and the else case). Thus, |
This comment has been minimized.
This comment has been minimized.
|
The meaning of being well-typed is fundamentally linked to run-time behavior. Milner [1] coined the phrase well-typed programs cannot 'go wrong', where going 'wrong' has a specific definition. A type system is sound if any well-typed program cannot go wrong. A type system is complete if any program that cannot go wrong is well-typed. The example program does not go wrong and therefore TypeScript is giving a false-positive by reporting a type error. [1] A theory of type polymorphism in programming (1978) |
This comment has been minimized.
This comment has been minimized.
paldepind
commented
Apr 18, 2018
|
That is a good quote. But, I think there's a difference between saying "well-typed programs cannot 'go wrong'" and saying "programs that don't 'go wrong' are well-typed". The implication is in different directions. The first statement is valid (Milner said it) but I don't think the second is. The example program doesn't go wrong. But that does not make it well-typed. A program is well-typed if it satisfies the typing rules of the given language. The example program does not satisfy the typing rules of TypeScript and thus it is not well-typed. @johanatan wrote that "the program should, in fact, pass the type checker" because the program doesn't go wrong. But, that is not the criteria that decide if a program should pass a type-checker. A program should pass a type-checker if it satisfies the typing rules of a language. And the program in question does not. Soundness and completeness are properties of the type system. Whether or not a program is well-typed is a property of the program in relation to the type system. These things are unrelated. As an example simply typed lambda calculus it both sound and complete. But, still it is easy to write a program in lambda calculus that doesn't 'go wrong' at run-time but which would still not be well-typed according to the simply typed lambda calculus type-system. That is an example of "doesn't go wrong does not imply well-typed". Anyway, it is possible that I may have gotten something wrong. In that case, please correct me |
This comment has been minimized.
This comment has been minimized.
|
Including perfect reachability analysis in a definition of completeness is very questionable because it implies that any truly complete type system could be used to e.g. prove or disprove the Collatz conjecture:
|
This comment has been minimized.
This comment has been minimized.
Yes this is exactly right; though I wasn't suggesting that they were the same.
I'm not saying that the definition of being well-typed is not going wrong. You're free to define it as you say. However that definition alone tells us very little; we can say that something satisfies the rules but what does that mean? How would we compare one checker for JavaScript with another? I could come up with a set of ad-hoc rules that let you write buggy programs that are technically well-typed, but my notion of being well-typed is inferior to TypeScript's. So it's helpful to have an external notion, which has long been the class of 'safe' programs that don't get stuck during evaluation. With that I can talk to someone about my system and tell them what it means to be well-typed in my system, without depending on the specific rules of my system. While we expect most systems to be sound with respect to this notion of 'safe programs', programmers have happily lived with the fact that we can't have completeness. Programmers wont be able to write programs they know to be safe because the type-checker will get in the way. Adding Turing Completeness to the type system isn't such a big deal because the undecidability can be exchanged for incompleteness by bounding the checker, and programmers have already been living with incompleteness. That was my only real point! @RyanCavanaugh Yes completeness gets thrown away once the source language is turing-complete, because for the type-system to be complete it would need to be able to solve the halting problem. |
This comment has been minimized.
This comment has been minimized.
johanatan
commented
Apr 18, 2018
|
I completely agree with what @jack-williams stated above and, yes, according to the rules of the TypeScript type system, what I stated was incorrect. However, the line between "compile-time" and "run-time" is quite blurred these days via several techniques: multi-stage programming & refinement types to name a couple. In the former, one can think of runtime as merely the final "stage" (and clearly from a human perspective we can reason at any number of meta- levels). |
This comment has been minimized.
This comment has been minimized.
aij
commented
Apr 18, 2018
|
@RyanCavanaugh Gödel's incompleteness theorems still apply here, via the Curry–Howard isomorphism. {Complete, Sound, Decidable}: Pick two. (or fewer) |
This comment has been minimized.
This comment has been minimized.
paldepind
commented
Apr 19, 2018
|
@jack-williams @johanatan I fully agree with both of your latest comments. So it seems that we agree
That is not the entire story though. You can have completeness, soundness, and decidability at the same time. Simply typed lambda calculus is a case in point. Then you have to forego Turing completeness. Sometimes that is the appropriate tradeoff. For instance in languages like Coq and Idris. You probably know that but I just wanted to point it out. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
fightingcat
commented
Jun 29, 2018
|
Came up a way to concatenate tuples in typescript@next, it's not practical, but I think it helps for people investing fun. Expecting typescript type system being better. type ToTuple<T> = T extends any[] ? T : any[];
type Head<Tuple extends any[]> = Tuple extends [infer H, ...any[]] ? H : never;
type Tail<Tuple extends any[]> = ((...t: Tuple) => void) extends ((h: any, ...rest: infer R) => void) ? R : never;
type Unshift<Tuple extends any[], Element> = ((h: Element, ...t: Tuple) => void) extends (...t: infer R) => void ? R : never;
type Push<Tuple extends any[], Element, R = Reverse<Tuple>, T extends any[]= ToTuple<R>> = Reverse<Unshift<T, Element>>;
type Reverse<Tuple extends any[]> = Reverse_<Tuple, []>;
type Reverse_<Tuple extends any[], Result extends any[]> = {
1: Result,
0: Reverse_<Tail<Tuple>, Unshift<Result, Head<Tuple>>>
}[Tuple extends [] ? 1 : 0];
type Concat<Tuple1 extends any[], Tuple2 extends any[], R = Reverse<Tuple1>, T extends any[]= ToTuple<R>> = Concat_<T, Tuple2>;
type Concat_<Tuple1 extends any[], Tuple2 extends any[]> = {
1: Reverse<Tuple1>,
0: Concat_<Unshift<Tuple1, Head<Tuple2>>, Tail<Tuple2>>,
}[Tuple2 extends [] ? 1 : 0];
type x = Concat<[1, 2, 3], [4, 5, 6]>; // [1, 2, 3, 4, 5, 6] |
This comment has been minimized.
This comment has been minimized.
aman-tiwari
commented
Jul 13, 2018
|
You can do math relatively efficiently if you instead use a LUT and do it digit-by-digit: |
This comment has been minimized.
This comment has been minimized.
pauldraper
commented
Jul 13, 2018
•
"Correct type check" means verify types according to the language specification.1 If you take "correct type check" to be "verify there are no runtime errors" ... then yes, no compiler of TS, Java, Lisp, COBOL or any other Turning-complete language can provide those assurances. It's such a meaningless interpretation as to be irrelevant. Type checking is performed according to the language specification, period. And in the case of TS, the language specification describes a Turing-complete type language (in addition to the obviously Turning-complete language itself). 1 https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md Though it's really out of date right now; I think the current spec exists only in the minds of TS authors (and maybe not even then). |
RyanCavanaugh
referenced this issue
Aug 29, 2018
Open
Type alias circularly references itself #14174
ahejlsberg
referenced this issue
Oct 10, 2018
Closed
Compiler hang on this complicated example #24016
fatcerberus
referenced this issue
Jan 29, 2019
Open
Wrongly infers any[] from Array.prototype.flat, Array.prototype.concat #29604
This comment has been minimized.
This comment has been minimized.
ritave
commented
Feb 5, 2019
•
|
I wanted to see how far can I push it and built a Bracket Expression verifier using @fightingcat 's Tuples and @hediet 's awesome example and automata. https://github.com/Crypto-Punkers/labs/blob/master/metaprogramming/src/brackets.ts One interesting thing I've noticed is that it's easy to lose typing information with Tuples, as the TSC will happy convert them into more general forms when for example passing it inside generics. type Tail<Tuple extends any[]> = ((...t: Tuple) => void) extends ((
h: any,
...rest: infer R
) => void)
? R
: never;
type IsEmpty<Tuple extends any[]> = {
true: "true";
false: "false";
}[Tuple extends [] ? "true" : "false"];
type Next<Tuple extends any[], TState extends State<any>> = {
value: Tail<TState["value"]>;
};
type SpecificTuple = [1, 2, 3];
type State<Tuple extends any[]> = { value: Tuple };
type Result = Next<SpecificTuple, State<SpecificTuple>>;
//Actual result:
// type Result = {
// value: any[];
// };
//Favourable result:
// type Result = {
// value: [2, 3];
// }; |
This comment has been minimized.
This comment has been minimized.
fightingcat
commented
Feb 6, 2019
type Next<Tuple extends any[], TState extends State<any>> = {
value: Tail<TState extends State<infer R> ? R : never>;
};@ritave You can change your Next to like this. |
hediet commentedMar 24, 2017
•
edited
This is not really a bug report and I certainly don't want TypeScripts type system being restricted due to this issue. However, I noticed that the type system in its current form (version 2.2) is turing complete.
Turing completeness is being achieved by combining mapped types, recursive type definitions, accessing member types through index types and the fact that one can create types of arbitrary size.
In particular, the following device enables turing completeness:
with
TrueExpr,FalseExprandTestbeing suitable types.Even though I didn't formally prove (edit: in the meantime, I did - see below) that the mentioned device makes TypeScript turing complete, it should be obvious by looking at the following code example that tests whether a given type represents a prime number:
Besides (and a necessary consequence of being turing complete), it is possible to create an endless recursion:
Turing completeness could be disabled, if it is checked that a type cannot use itself in its definition (or in a definition of an referenced type) in any way, not just directly as it is tested currently. This would make recursion impossible.
//edit:
A proof of its turing completeness can be found here