Every other week, the Principle Studios engineering team gets together and we challenge ourselves to accomplish a tiny task given by another team member. When we’re done, we share the results of the challenge and try to learn from each other - since there’s rarely one “right” way to accomplish even technical tasks. Sometimes these are simple, such as “write the types for this concept in TypeScript”, “use this feature in C# to accomplish this task”… and sometimes they’re a bit more open-ended.
This week, we were practicing generics in TypeScript, especially with regards to tuples:
TypeScript introduced the Tuple type long ago, and have added functionality to them ever since. It’s been necessary, since JavaScript developers will often use the order of arrays to convey data (like our challenge from March 3rd!) However, working with those data types can be challenging. This week’s TypeScript challenge will cover generics, and has two parts.
A couple example tuples for use with the challenges below:
type MyTuple = [number, string]; type MyOtherTuple = ['foo', 'bar', 'baz'];
The first part: Write a
First<T>
type that evaluates to the first type in the tuple. For example, using the above types:// TODO type First<T> = never; // Temp should be `number` type Temp = First<MyTuple>; // Temp2 should be `'foo'` type Temp2 = First<MyOtherTuple>;
The last part, for those who are looking for an even greater challenge: Write a
Last<T>
type that evaluates to the last type in the tuple. For example, still using the above types:// TODO type Last<T> = never; // Temp3 should be `string` type Temp3 = Last<MyTuple>; // Temp4 should be `'baz'` type Temp4 = Last<MyOtherTuple>;
Entries
This week, almost everyone who entered gave both solutions. Since many of them were similar, I won’t list out who gave what but will instead break down the solutions.
First<T>
First<T>
has two basic solutions, the simplest being the following:
type First<T extends unknown[]> = T[0];
This solution is fast and cannot be used erroneously - by adding the extra check
into First
, we ensure it can only be used correctly, which allows for the
simple expression of T[0]
, which evaluates the type of the first element.
unknown
is a nice touch here; it means we don’t need to know the type of the
array.
Another solution for First<T>
makes use of the same TypeScript features we’ll
use in the Last<T>
solution:
type First<T> = T extends [infer Car, ...unknown[]] ? Car : never;
This works in all the cases given above, though using infer
is a bit slower in
current versions of TypeScript - it triggers an alternate evaluation path, which
can lead to complex types failing to be infered. (I haven’t done it with this
type specifically, but I’ve run into the “Expression produces a union type that
is too complex to represent” error on multiple occasions.)
However, I’ve left out the constraint on the generic to demonstrate another
issue on this one: it may surprise you that First<string | [number, string]>
evaluates to number
. This is because string
does not extend the given tuple,
evaluating to never
, and number | never
simplifies to number
. Let this be
a reminder that, while extends/infer is powerful, constraints are still
important.
(And yes, Car is a reference to CAR/CDR.)
Last<T>
In case you hadn’t inferred (pun only slightly intended) the answer from the
last solution, Last<T>
can be implemented as follows:
type Last<T extends unknown[]> = T extends [...unknown[], infer Tail]
? Tail
: never;
There seems to be no way to get the last item without an infer, so we must use
it here. Also unfortunately, while Last<string[]>
still passes the constraint,
it also still evaluates to never
, however it does match all the given examples
in the original prompt. Are there any clever TypeScript engineers who want to
solve that last challenge to improve the constraint further?
Thanks to all our participants!
Thanks again to Tony Mishler, Aaron White, Andrew Owen, Justin Rodriguez, Ben
Cohen-Chen, Daniel Kurfirst, and Megan Kossa for attempting the challenge this
week - Last
was a fun challenge for me, too.
Thanks for reading, and I hope everyone learned something - I know I did!