Impossible state impossible
Today I'll show you how to easily and safely fix the "function may return undefined" error. That's right and wanted behavior, but not in case you know that you never get that undefined value. You may experience this especially when using functions like head
,
last
or find
.
Here's the common definition of a head
function:
const head = <A>(a: A[]): ?A => a[0]
head(['foo', 'bar']) // -> 'foo'
head([]) // -> undefined
which we want to use in composition as follows:
pipe(head, toLower)
^^^^
but it's complaining that undefined is not compatible with type string. That's absolutely correct, but in our case we know that we won't pass any empty array. So head will never return undefined, therefore types are compatible...
How to solve it?
Unsafe
First solution is to introduce a "type polishing" identity function, that simply says "I know what I'm doing here, right?". This function does nothing with value, as it is never undefined, but does some type casting just for the type checker.
The name unsafe
is not picked accidentally, but it's purpose is to to have clear indication that something in composition needs our attention:
const unsafe = <A>(value: ?A): A => ((value: any): A)
pipe(head, unsafe, toLower)
Note that it's completely up to us to check the data that goes to the composition. Type checker won't help as we shut it up. Cognitive load is high (and such a program can have runtime errors).
Impossible state impossible
What if we are looking at the problem from wrong direction?
We know that the array we are passing to the composition will never be empty - it will always contain at least one element.
Obviously, the type A[]
does not guarantee this property. But what if we introduced structure, a type, that will conform and guarantee it for us:
type NonEmptyArray<A> = {|
head: A,
tail: A[],
|}
const head = <A>({ head }: NonEmptyArray<A>): A => head
Now it's impossible to represent invalid state - an empty array. It also allows us to remove all "unnecessary" checks and reduce the amount of cognitive load needed to understand the program.