Mid-week Scoop: Comparing Functions in Typescript
Taking a look at how Typescript compares functions to check whether one function type is assignable to another function Type.
In a previous issue, I looked into structural typing in Typescript but focussed on object literals mostly (you can find the previous article here). In this scoop, I want to look closely at something closely related to structural typing in Typescript and how comparing functions works in Typescript.
So, without further ado:
So, how does Typescript compare two functions and determine whether they are assignable to each other? Let’s take the following function Types:
type F1 = () => void;
type F2 = (input1: string) => void;
In Typescript, we can assign a variable typed as F2
above to FI
, but not vice versa:
let f1: F1 = () => {
console.log("F1")
}
// this is okay
let f2: F2 = f1;
// this will error
let f3: F1 = f2;
// ^ Type 'F2' is not assignable to type 'F1'
Why? In this case, typescript mirrors this behavior from Javascript, where it’s quite common to ignore extra parameters of functions when we are not using them. Remember, Typescript compiles to Javascript and adds a typing system (mostly) on top of the features that Javascript provides; this means mirroring some of the behavior from Javascript whenever it doesn’t compromise Type Safety.
The reason for this assignment to be allowed is that ignoring extra function parameters is actually quite common in JavaScript. For example,
Array#forEach
provides three parameters to the callback function: the array element, its index, and the containing array. Nevertheless, it’s very useful to provide a callback that only uses the first parameterLink to docs.
Typescript will not allow you to provide function parameter types that do not match the parameter types in each position. If we tried to do the opposite, assigning a function typed as F2
to another function typed as F1
, then Typescript will complain.
let f1: F1 = () => {
console.log("F1")
}
// this is okay
let f2: F2 = f1;
// this will error
let f3: F1 = f2;
// ^ Type 'F2' is not assignable to type 'F1'
With the F2
type having a required input parameter - input1
- it makes sense since the F1
type doesn’t have any parameters.
Optional Parameters
If we made the input1
parameter optional for the F2
function, then typescript would stop complaining.
type F1 = () => void;
type F2 = (input1?: string) => void;
let f1: F1 = () => {
console.log("F1")
}
// this is okay
let f2: F2 = f1;
// this is okay now
let f3: F1 = f2;
This behavior is also mirrored from Javascript behavior, and since optional parameters are undefined when they are not supplied/provided in Javascript, hence why Typescript allows this behavior. This behavior is unsound from a type-system perspective. Still, one that is allowed by the Typescript Type System, which has to take into account the runtime behavior of Javascript, and this is why the Typescript system is said to be unsound; I will explore this in a future issue.
Conclusion
In this week’s scoop, we covered structural typing in functions. Understanding this behavior is essential when writing and reading Typescript code, and it will help you form a much better mental model for what to expect from Typescript as you continue working with it.