In these examples we'll be using letters to represent any number of variables of any type: a
, b
,c
, etc.
Functional programming is nice becuase it doesn't discriminate primitives and objects.
The point of all these type containers mentioned below is to provide safer and consitently typed code. Prerequisite is that you know what piping means and does.
If you have worked with JavaScript at all in the past, it is very likely that you have come across a TypeError at some time (other languages will throw similarly named errors in such a case). Usually this happens because some function returns null or undefined when you were not expecting it and thus not dealing with that possibility in your client code.
Not using null or undefined results in handling your errors and providing safe defaults when things don't go as expected.
So what do we do to be functional? Use Option
instead.
For the imperative, Option<a>
indicates that a
can be interpreted to be a
, null
or undefined
.
For the functional, it represents that it could be one of two types: Some<a>
or None
.
If your option type becomes Some<a>
, unfolding it will always be of type a
.
If your option type becomes None
, unfolding will result in undefined
or null
and you should provide a default value and/or handle the error.
If Typescript doesn't which it will be at compile time, it will be Option<a>
.
Take this function. Randomly outputs a number
or null
, 50% of the time.
You never know which one it will be until it is run.
How to turn handle this? I propose we use 0
as the default number, so it is always a number.
// randomizer.ts
export const random = (): number | null => {
const value = Math.random()
return value > 0.5 ? value : null
}
import { random } from './randomizer';
const a = random()
const b = a !== null? a : 0
import { random } from './randomizer';
import { option } from 'fp-ts';
import { pipe } from 'fp-ts/lib/pipeable';
const a = random()
// long hand syntax - to demonstrate the fundamental.
const b = pipe(
a,
option.some,
option.fold(
() => 0,
c => c
)
)
// short hand syntax - preffered for brevity.
const c = pipe(
option.some(a),
option.getOrElse(() => 0)
)
Constants b
and c
yield the same result, but with different functions derived from the option
module.
Experienced functional programmers tend create shortcuts to patterns that they find themselves repeating.
You'd already seen that in the example above, with option.getOrElse(() => d)
is analagous to option.fold(() => d, e => e)
.
We have a few options when dealing handling conditional values.
Summary of usage:
- Use
Option
when the return value/s will be the same value, and the same type. - Use
Either
when the return value/s will be different values, and the same type.
const doSomething = (a: number | string | null ): number => {
if (!a) {
return 0
}
else if (typeof a === 'string') {
return Number(a)
}
else {
return a
}
}
const doSomething = (a: number | string | null ) =>
pipe(
a,
option.fromNullable(b => b),
option.chain(b => option.fromPredicate((c): c is number => ))
)
MAP! map all the things.
if using Array.prototype.map, use array.map
if using Array.prototype.[forEach, reducer*]
, use array.reduce.