It’s time for one of the most useless things I ever made — something beyond reason, where the logic stops being rational and the void is the reality. It’s time to create a calculator using only types from Typescript.
A few days ago, I remembered a discussion with a friend about Turing-complete systems. We checked Wikipedia article on the subject, and we discovered that many things (like games) were Turing-complete. So yes, Minecraft is Turing-complete, Factorio too, Dwarf Fortress too, and it seems that Habbo Hotel and Magic: The Gathering (yes, the cards game) are also Turing-complete.
We also found somewhere that the type system of Typescript is Turing-complete. With this knowledge, I decided to start developing something that would not change anything: a calculator with only Typescript types.
Yes. This is one of the most stupid ideas I got for a while. But this is a fun exercise you can do to prove your skills in typing everything.
The rules
If you want to try to do the calculator, here are the rules of the “game” (if we can call this a game):
- you have to start with only two types:
BZero
(for Boolean Zero) andBOne
(for Boolean One).
- you can only use types. No interface, no enum, only types.
- you have to compute the four basic mathematical operations (with types): add, subtract, multiply and divide. You can also add modulo.
- this code should works:
Due to the way I implement the calculator, I use another type Result
to read the result:
This type is totally optional in the rules. But you must have a exact number type at the end of the computation. For example, you should have:
And here you go! It’s time for you to code!
…
…
…
Yeah, better read the solution, isn’t it?
The solution
Conception
Firstly, I search what the ways to get a number type from a type in Typescript are. You have your base operation to transform BZero
and BOne
to Zero
and One
number if you know this.
I tried something based on array indexes, but there was no way to get a number. So I discovered that Typescript tuples have a length
property. And it works with variable tuples since 3.0. With this information, I began to write a calculator based on Tuples.
The first types we need are the Zero
and the One
based on tuples. In this way, I write a simple conditional type that returns a tuple with the number of elements equivalent to the number it describes. For example :
In this way, we need a type to read the length of the tuple to get the desired type number.
Operation 1 : Add
With this conception, the Add
type is the most straightforward operation to implement, thanks to Typescript 4.0. We have to concatenate the two tuples, and we have done an Add
type.
Operation 2 : Subtract
The hard way
Now, the fun begins.
We need to think about the computation behind the subtraction. So let’s come back to elementary school.
I have 6 balloons. I give 4 balloons to Manu. I now have 2 balloons remaining.
In terms of tuples, we can illustrate the problem with this :
To implement the subtraction, I take each element of MyBalloons
one by one and check if each element exists in NewManuBalloons
. When I arrive at the moment there’s no more element in NewManuBallooons
, I start to increment a variable which will be the result.
But there’s a problem: it looks like a loop. How can we achieve a loop in Typescript?
The answer here is to use a recursive type. And with a condition, we can do that with a recursive conditional type. Here’s the code:
Oh yeah, there’s another tip: I use
type inference to remove elements of a tuple. For example, if I have type A = [true, true, true]
, I will get type H = [true, true]
. This is because I check if A
’s first element is a boolean, and I send the rest of the tuple in another type variable.
I use the type inference in a second condition to check if the current element exists in B
. When there’s an element in A
and B
, I recall the Subtract
type with the remaining elements of A
(H
) and the remaining elements of B
(J
). If there’s no more element in B
, we continue to call Subtract
, but we increase Res
at each call. And when A
is empty, we return Res
type.
And now, we have a subtraction with only types. Note that this subtraction only works with positive integers. This problem is caused by tuples, because yes, you cannot have a tuple with a negative length.
The smart way
The hard way is a pure example of overengineering. Because there’s such a simple method to do a subtraction: subtract B
from A
and keep the result.
For my defense, I understood that I could use type inference between 2 dynamic types when working on Divide
. But you can shame me, and it’s okay.
Here’s the code:
Operation 3 : Multiply
We already have seen all the techniques we need to Multiply. So we need to add B
times the A
number. This is now a piece of cake since the subtraction’s hard way.
To multiply, make a loop (with recursive types). In this loop, remove One
from B
, add A
to the result, and send the Res
when B
is Zero. In this way, we will add A
B
-times, so we are doing A
x B
.
Operation 4 : Divide
We have worked since the beginning with integers. So we can only have a Euclidian division for this part.
In the subtraction, we search if A
can contain B
and what’s remaining. We will do the same thing, except we need to search how many times A
contains B
. So we have the same primary condition as the subtraction, but we add a recursive type with an incremented result at each loop iteration. When A
cannot contain B
anymore, send the number of loops done until here.
Operation Bonus : Modulo
Modulo is almost the same as the Divide. We keep checking how many times A
contains B
, and when we reach the end, we return the remaining value instead of the number of iterations. And tadaaaaaa, we get the modulo operation.
So here we have a working calculator with only Typescript types. I thought about implementing the decimal calculator based on the binary representation of decimals. But this is clearly madness. I hope you enjoy reading this piece of programming like I enjoyed writing it. And last but not least, here’s the topic of the initial discussion I had with my friend about Turing-Complete Typescript.
Thanks for reading! From all of us here, I want to wish you happy programming and God Bless, my friend! :)