Everyone’s excited about incremental builds in the upcoming TypeScript version. However, there is another, at least as interesting, feature in this release. Hidden at the very bottom of the TypeScript 3.4 RC announcement lies a humble section called Propagated generic type arguments.
In fact, Anders Hejlsberg himself is excited about this feature 😉
Really excited about this one…https://t.co/bbTF9bdFO5
— Anders Hejlsberg (@ahejlsberg) March 4, 2019
So, what’s the meaning of this enigmatic update in TypeScript?
Note: at the moment of writing TypeScript 3.4 is still a Release Candidate. You can install it by running npm install typescript@next
.
Example
Let’s have a look at the following example. Imagine that you’re fetching a collection of objects from some backend service and you need to map this collection to an array of identifiers.
1 | interface Person { |
Next, you decide to generalize getIds
function so that it works on any collection of objects having the id
property.
1 | function getIds<T extends Record<'id', string>>(elements: T[]) { |
Fair enough. However, the code for this simple function is quite verbose. Can we make it more concise?
Pointfree style
Sure, we can take advantage of a functional programming technique called pointfree style. Ramda is a nice library that will let us compose this function from other functions: map
and prop
.
1 | import * as R from 'ramda'; |
map
is partially applied with a mapper function prop
which extracts the id
property from any object. The result of getIds
is a function that accepts a collection of object. You can read a more detailed explanation in my article about pointfree style.
Sadly, TypeScript (pre 3.4) has bad news for you. The type of getIds
is infered to (list: {}) => {}
which is not exactly what you’d expect.
You can explicitly type map
but it makes the expression really verbose:
1 | const getIds = R.map<Record<'id', string>, string>(R.prop('id')); |
This is where propagated generic type arguments come in. In TypeScript 3.4 the type of getIds
will correctly infer to <T>(list: readonly Record<"id", T>[]) => T[]
. Success!
Propagated generic type arguments
Now that we now what propagated generic type arguments is about, let’s decipher the name.
R.map(R.prop('id'))
is an example of a situation when we pass a generic function as an argument to another generic function.
Before version 3.4 TypeScript the type of parameters of inner function type was not propagated to the result type of the call.
Why should I care?
Even if you’re not particularly excited about pointfree style programming, bear in mind that some popular libraries that rely on function composition and partial application and will also benefit from this change.
For example, in RxJS it is possible to compose new operators from existing ones using pipe
function (as opposed to pipe
method). TypeScript 3.4 will certainly improve typing in such scenarios.
Other examples include Redux (compose
for middleware) and Reselect.
Summary
Introduction of propagated generic type arguments has significany consequences for pointfree style programming. Before this update, using libraries such as ramda
or lodash/fp
with TypeScript was really cumbersome - you had to explicitly provide type arguments to certain calls which made the code far less readable.
TL;DR: Propagated generic type arguments pave the way for wider adoption of functional programming techniques in TypeScript.
Cover photo: JamesDeMers from Pixabay.
Did you like this TypeScript article? I bet you'll also like my book!
⭐️ Advanced TypeScript E-book ⭐️