Skip to content

Instantly share code, notes, and snippets.

@waynevanson
Last active March 30, 2020 07:04
Show Gist options
  • Save waynevanson/9073f53ee692ac2a3af36e284b79051c to your computer and use it in GitHub Desktop.
Save waynevanson/9073f53ee692ac2a3af36e284b79051c to your computer and use it in GitHub Desktop.

fp-ts

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.

null or undefined values

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.

Option<a>

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>.

Example

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
}
Imperative
import { random } from './randomizer';

const a = random()
const b = a !== null? a : 0
Functional
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).

conditionals (if, else if and else)

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.

Usage

Imperative
const doSomething = (a: number | string | null ): number => {
  if (!a) {
    return 0
  }
  
  else if (typeof a === 'string') {
    return Number(a)
  }
  
  else {
    return a
  }
}
Functional
const doSomething = (a: number | string | null ) =>
  pipe(
    a,
    option.fromNullable(b => b),
    option.chain(b => option.fromPredicate((c): c is number => ))
  )

for loops

MAP! map all the things.

if using Array.prototype.map, use array.map if using Array.prototype.[forEach, reducer*], use array.reduce.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment