
TypeScripts Type System Is Turing Complete - supermdguy
https://github.com/Microsoft/TypeScript/issues/14833
======
Gehinnn
When I discussed my discovery with a friend of mine who is doing his PhD, he
was not really excited about it and once I showed him the features of
TypeScripts typesystem, he immediately explained me why it has to be turing
complete. So in theory, this result is nothing new and nothing "newsworthy".

However, even being myself a CS student close to finishing my BA degree and
stronlgy interested in CS theory, I didn't know that result and I couldn't
imagine how powerful TS's typesystem is. So, there actually are a lot of
people who underestimate the power of typescripts typesystem completely and
who could write even safer code if they knew about it.

I don't know exactly anymore how I "rediscovered" this turing-completeness-
property, but at that time I was developing a "fully" typed SQL builder as an
alternative to knex ([https://github.com/hediet/ts-typed-
sql](https://github.com/hediet/ts-typed-sql)) and did some research on how to
make certain SQL constructs typesafe in TypeScript. For example, one thing I
needed, was a type RecordTypeToJson<T> which could recursively transform a sql
record type T to a proper json type: [https://github.com/hediet/ts-typed-
sql/blob/master/src/AST/T...](https://github.com/hediet/ts-typed-
sql/blob/master/src/AST/Types.ts#L68)

Without the devices discovered in the TC-proof I wouldn't have been able to
implement that particular type. And once I recognized the power of TS, I found
some other useful devices, e.g. how to use overloading to report custom error
messages for invalid types ([https://github.com/hediet/ts-typed-
sql/blob/master/src/AST/Q...](https://github.com/hediet/ts-typed-
sql/blob/master/src/AST/Queries/SelectQuery.ts#L89)) or how to track whether
more than one column is selected ([https://github.com/hediet/ts-typed-
sql/blob/master/src/AST/Q...](https://github.com/hediet/ts-typed-
sql/blob/master/src/AST/Queries/SelectQuery.ts#L57)).

I hope that these ideas inspire other libraries as well!

~~~
bad_user
A turing complete system doesn't necessarily mean it's powerful, or useful, it
just means that it's equivalent with a turing machine and in the case of type
systems, that usually happens because general recursivity is allowed.

And that can also mean that it is error prone.

~~~
fauigerzigerk
It doesn't mean useful, but in my opinion it definitely means powerful. Being
expressive enough to describe any possible algorithm is a power. No self
respecting super hero would deny that, not even TypeScript's worst enemy.

But with every power comes a weakness. Compile times can no longer just seem
endless. They can actually be endless, and there is no way to prove otherwise.

What I find unconvincing about many Turing complete type systems is that
pragmatically speaking they are really shitty general purpose programming
languages.

If we create Turing complete type systems and suffer the consequences, why not
go all the way and make it actually practical to write proper meta programs
the way Lisp has done?

~~~
bad_user
I kind of disagree, describing any possible algorithm is not that powerful,
given that an actual Turing machine can do it.

And along with Turing machines you've got problems, like undecidability and
the halting problem.

------
lopatin
Gotta mention Shen here, the language with an intentionally Turing complete
type system. You can write your types as Horn clauses.

[http://shenlanguage.org/](http://shenlanguage.org/)

------
hprotagonist
[https://www.destroyallsoftware.com/talks/the-birth-and-
death...](https://www.destroyallsoftware.com/talks/the-birth-and-death-of-
javascript)

Closer and closer to reality. ;)

------
evadne
Related: [https://aphyr.com/posts/342-typing-the-technical-
interview](https://aphyr.com/posts/342-typing-the-technical-interview)

~~~
johnny_reilly
Oh I love this - thanks for sharing!

------
the_common_man
Here is the proof
[https://gist.github.com/hediet/63f4844acf5ac330804801084f87a...](https://gist.github.com/hediet/63f4844acf5ac330804801084f87a6d4)

------
geofft
Has anyone ported Aphyr's type-level 8-queens code
([https://aphyr.com/posts/342-typing-the-technical-
interview](https://aphyr.com/posts/342-typing-the-technical-interview)) to
this typesystem?

I tried porting it to Rust a bit back but got stymied by, uh, the typesystem
trying to resolve equality in the wrong direction (by adding type constructors
instead of removing them) or something. I guess that's UndecidableInstances
maybe?

------
lordleft
CS student here, why is Turing completeness a bad thing in this case?

~~~
unholiness
Turing complete systems are highly capable, and thus, are harder to prove
anything about. The halting problem notwithstanding, there's first the "there
are things that don't halt" problem: that is, you can write a valid TypeScript
program that will never finish compiling.

Now, this really shouldn't be an issue unless someone is deliberately abusive.
Java's generic types are also Turing complete[0], and it hasn't been
problematic. Just because an abusive program exists doesn't mean it will be
written.

But who am I kidding... this is the JavaScript community. I'll give it 2 weeks
until someone releases a framework allowing you to write functions that don't
compile unless their input is a prime number.

[0]
[https://arxiv.org/pdf/1605.05274.pdf](https://arxiv.org/pdf/1605.05274.pdf)

~~~
flashmob
> you can write a valid TypeScript program that will never finish compiling

Sure, typescript program may never finish compiling, although it will still
halt: Just `kill -9`. The halting problem solved! ;-)

Seriously though, there are many languages / systems that are 'accidentally
Turing complete', including CSS + HTML, and it doesn't really impede them. See
[http://beza1e1.tuxen.de/articles/accidentally_turing_complet...](http://beza1e1.tuxen.de/articles/accidentally_turing_complete.html)

~~~
naasking
> Sure, typescript program may never finish compiling, although it will still
> halt: Just `kill -9`. The halting problem solved! ;-)

The problem is that you can't really tell when a long compile is an infinite
or just a really long one. You want non-Turing completeness because your tools
need to be reliable.

~~~
flashmob
Turing complete means that it's a simulation of a Turing machine, but not
actually one. A TM is something purely abstract that was invented to prove the
halting problem, and infinity of the tape is arguably the most crucial
concept. Computers do not have infinite memory / power / time so the halting
problem doesn't really make sense unless you talk about a pure Turing machine
with infinite tape.

In most cases, you know something is wrong once your typescript compilation
takes more than a few seconds. At most, it could be an interesting DOS attack,
but most continuous integration systems should have a limit on how much
seconds they can run the compiler for. So it's really not a problem if you
have constraints on resources...

~~~
naasking
Except you know no such thing, that's the point. That could very well be the
intended and correct behaviour of the compiler for that input.

~~~
flashmob
Sure, I can see your point. It's a problem for computer scientists, and good
to know as an anecdote. I'm talking about it in practice - when using
TypeScript the 'halting problem' has never been an issue. I was joking about
the 'kill -9' but also meant it in a half-hearted way!

So, if not turing complete, what kind of automata would you use to accept your
language, say if you were designing your own language?

------
lewisl9029
Not entirely on topic, but I'm curious if anyone could comment on the
similarities and differences between TypeScript and Flow in their more recent
iterations. I've been doing a bit of research and it seems like they've grown
to become fairly similar in terms of syntax and overall capability, but I'd
love to hear if anyone has compelling reasons as to why you'd choose one over
the other.

Lately, I've been evaluating Relay Modern for a new project, and one of its
new features is the ability for the Relay Compiler to generate Flow type
definitions based on your GraphQL schema, which makes for a very compelling
case to choose Flow if I were to go with Relay Modern for the project. Are
there similar tools for generating TypeScript or Flow type definitions from
GraphQL schemas for use in the Redux + Apollo ecosystem?

Also, do either type system have a story for leveraging type definitions to
perform generative testing like you could in Clojure with Spec?

~~~
slackingoff2017
The two are converging in a lot of ways. Flow maintains it's impressive
ability to determine types based on individual usages of functions. Basically
checking type safety based on what you passed in rather than what the function
says it's supposed to take.

Typescript is more of a traditional typing system and discourages you from
using variable types in the first place.

The important distinction is that if you tell typescript a function can take
in any value it makes no attempt to type check. Flow will look at each usage
of the function and try to determine if the value being run through is valid.

My personal preference is to typescript so I'm probably biased. Typescript
community, tooling, and general ease of use is better. MS has long been known
for good dev tools and it shines here.

If you end goal is to statically type your whole application then flow has
little or no advantages. In this case it's better to go with Typescript for
ease of use, community support, friendlier error warnings, and better tooling.
If your goal is to keep using JavaScript and use type checking as a fancy
linter then flow is clearly superior.

Something else I noticed that may be very important down the road: typescript
is easier to get rid of. The JavaScript it outputs is very readable and mostly
looks hand-written. IMO the best tools are the ones easy to get rid of,
especially since JS is such a moving target. If Typescript dies one day you
can compile down to JS one more time and throw the TS code away and still have
a decent codebase. Flow and Babel produce some really crazy stuff, and codegen
of clean and readable JavaScript seems like more of an afterthought.

~~~
davnicwil
> If Typescript dies one day you can compile down to JS... flow and babel
> produce some really crazy stuff

I'm not sure if I'm misinterpreting what you're saying here, but on the face
of it, this is not correct. Flow is just annotations over JS, it is not a
compile to JS language like TS.

Flow doesn't produce anything. It's absolutely trivial to remove by just
stripping the annotations. There is a babel plugin for doing _that_ , but
that's all it does. If there's any output after that that doesn't seem to map
well to the original JS code, that's all babel on the remaining JS, nothing to
do with Flow. It would be the same with or without Flow. Or did you mean
something else?

~~~
slackingoff2017
Typically you use flow with Babel so you can use newer JavaScript features.
Babel creates messy JavaScript in the process of transpilation.

Typescript combines typing and transpilation together, essentially doing what
flow+Babel does. Typescript allows you use newer JS features like Babel but
produces much cleaner JavaScript.

If the choice is Typescript or flow without Babel Typescript is the winner by
a mile. Without Babel hooked up to flow you can't use any new JS features.

~~~
davnicwil
Ah, sure, that makes sense.

A couple of things which mitigate that downside with babel though, at least
for my setup & way of working:

(1)

Sourcemaps. For me, the only practical purpose of touching the transpiled code
is debugging. For this purpose I find the sourcemaps output by babel gives me
1-1 mapping to my source anyway, pretty flawlessly with chrome dev tools. So,
I have no issues with the messiness or non-messiness of the transpiled code.

(2)

With babel, your source _is_ just JS. Eventually, at some glorious point in
the future, we _will_ have enough of those great features available natively
in enough browsers that it's possible to turn babel transpilation off.

At that point, Flow has a massive ace up its sleeve in terms of being just an
additional layer on top of JS - the build step will be incredibly fast and
tiny. Typescript on the other hand will never be just JS. There will never be
a total 1-1 mapping (TS is, by design philosophy, a superset of JS), and even
if it gets really fast there will always be more of a transpilation step,
always source mapping, etc. It will never go away.

So, TS feels like a nice packaged solution now, but will it look so attractive
in 5 years? Or maybe a touch over engineered for a problem that doesn't really
exist any more? In this sense, to me, Flow feels a bit more future-proof.

------
hellofunk
Okay, but you know what else is Turing complete? BASIC. Bash script. Assembly.
In 2017 I'm not sure that something being Turing complete should be considered
a shocker, or even that meaningful.

Heck, I've seen people write whole programs just using clojure.spec which is
supposed to be only for type annotations.

~~~
neoeldex
Brainfuck is also turing complete ;)

~~~
hellofunk
I suspect whoever is downvoting you is not aware that Brainfuck is a real
programming language :)

~~~
djgs
I think the joke is that the instructions in Brainfuck are the same as a
Turing Machine. Tape left, tape right, increment, decrement etc.

------
catnaroek
Why is this considered newsworthy? How many practical type systems do you know
that are _not_ Turing-complete, yet powerful enough as development aids?
(Hindley-Milner doesn't count. HM might have decidable type checking, but
actual programming languages don't just use HM. In particular, OCaml and GHC
Haskell do _not_ have decidable type checking.) Heck, maybe computer
scientists ought to do more research into non-Turing-complete development
tools.

~~~
Drup
OCaml's _type_ system is not turing complete (unless you have a new striking
example to provide, but this would be considered a critical soundness bug).

There are two undecidable bit in OCaml's typechecker: 1) Checking module
types, due to the possibility of naming and manipulating type signatures, in
particular in functors. It's extremely hard to hit by accident, since
computing in signatures is quite unidiomatic (and unwieldy). 2) Exhaustiveness
of pattern matching for GADTs. You can hit that one (although, not "by
accident") fairly easily, so the exploration is bounded and always terminate.

As for Haskell, the base system for typeclasses is not turing complete, you
need a specific combination of extensions which are known to be dangerous
together.

Finally, to answer the first question ... Most dependently typed languages
(Coq, Agda) are not turing complete. People who do research in type systems
are usually rather careful about the whole thing.

