Skip to content
Advertisement

Type-level Catalan function in TypeScript

Consider the following Catalan function in JavaScript.

JavaScript

It returns all the possible ways of associating n applications of a binary operator, where n = xs.length.

I want to do something similar, but with types in TypeScript. However, I don’t know how to implement the “else” branch.

JavaScript

Any help will be greatly appreciated. By the way, I want to use this Catalan type to define the following function.

JavaScript

Here’s how the flatten function is implemented in JavaScript.

JavaScript

Edit: If it helps, here’s an implementation of the value-level catalan function in Haskell.

JavaScript

Here’s the output of the above Haskell program.

JavaScript

Advertisement

Answer

updated may 19

Oh boy, we aren’t done yet. We can make this thing even faster!

The first thing you can do is transform the extends in Catalan to only:

JavaScript

This makes it extremely fast. It’s only a lookup table now.

Then instead of big clunky loop for CatalanLoop, we can use distributive conditional types!

JavaScript

And you’ll notice a new type to help with the distributing:

JavaScript

Try this new playground to see the effects of these changes.


When encountering type-level problems like these, it’s best to look at the original code and look for patterns, or anything that the type system can do for you.

So let’s begin:

JavaScript

First we notice that if xs is empty, then we directly return x. We make a mental note to use XS["length"] extends 0 ? X : ... later.

Then we see that this:

JavaScript

is really just partitioning xs in such a way that:

JavaScript

In other words, we split the tuple at index 3 and return the two halves. This will be much faster than slicing the tuple two times individually and can be implemented without much trouble.

Finally we notice this nested loop:

JavaScript

No need for this in the type system, we can simply do:

JavaScript

and have it generate all the possible pairs for us from the unions.

Alright, time to get cracking away at the solution.

Recall that x is returned if xs is empty:

JavaScript

And also when XS is only one element then we return that pair. If XS has more than one element, we enter the loop instead:

JavaScript

Let’s see the loop now:

JavaScript

Now, what’s this funny looking thing:

JavaScript

keyof XS would give us something in the form of number | "0" | "1" | "2" | "at" | "concat" | "...", but we only want the indices of XS. If we intersect keyof XS with the interpolated bigint, we get the desired "0" | "1" | "2" only.

That means this is just like the loop in the original code! We loop over each index using a mapped type.

Inside the loop body we partition XS at index K:

JavaScript

But we have to assert to TypeScript that our partitioning type will definitely give us tuples like this first:

JavaScript

Then we call Catalan and make our pairs:

JavaScript

This is doing what this original code does:

JavaScript

And let’s close off all our ternaries/conditionals with never (because these clauses should never be reached anyways):

JavaScript

Finally, we need to make our partitioning type.

To do that, we need a type to increment a number. This can be done with a tuple like this:

JavaScript

Now that we can increment a number, we define Partition:

JavaScript

This type loops over XS until it hits At, the index to partition at. It excludes the element at At and stops, giving us [Left, Rest], the two halves. Partition is the type that replaces xs.slice(0, i) and xs.slice(i + 1).

Lastly, just for kicks, let’s also make a type to mimic the original show function:

JavaScript

And wow! It really does work!

JavaScript

To end off this little adventure, a playground where you can play around with this yourself.

User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement