Code Conventions

Table of contents

Module structure

In general a module containing the definition of a data structure has the following structure

  • URI definition and module augmentation
  • data structure definition
  • companion functions
  • instance functions (private)
  • type class instance definitions (either constants or functions)

FAQ

What a C suffix means, e.g. Functor2C vs Functor2

The naming convention is:

  • the number means the kind
  • C means Constrained
Kind Type class Type defunctionalization Note
all Functor<F> HKT<F, A>  
* -> * Functor1<F> Kind<F, A>  
* -> * -> * Functor2<F> Kind2<F, E, A>  
* -> * -> * Functor2C<F, E> Kind2<F, E, A> A variant of Functor2 where E is fixed
* -> * -> * -> * Functor3<F> Kind3<F, R, E, A>  
* -> * -> * -> * Functor3C<F, R, E> Kind3<F, R, E, A> A variant of Functor3 where both R and E are fixed

Example Functor

The base definition

export interface Functor<F> {
  readonly URI: F
  readonly map: <A, B>(fa: HKT<F, A>, f: (a: A) => B) => HKT<F, B>
}

The definition for type constructors of kind * -> * -> * (e.g. Either)

export interface Functor2<F extends URIS2> {
  readonly URI: F
  //             v-- here E is free
  readonly map: <E, A, B>(fa: Kind2<F, E, A>, f: (a: A) => B) => Kind2<F, E, B>
}

The definition for type constructors that start with kind * -> * -> * but need to be constrained in order to admit an instance (e.g. Validation).

//                           this fixes E --v
export interface Functor2C<F extends URIS2, E> {
  readonly URI: F
  readonly _E: E
  //                                v-- here E is fixed ---------------v
  readonly map: <A, B>(fa: Kind2<F, E, A>, f: (a: A) => B) => Kind2<F, E, B>
}

For example, Validation admits a Functor instance only if you provide a Semigroup instance for the failure part

//   this fixes E --v                                            v-- here E is fixed
const getFunctor = <E>(S: Semigroup<E>): Functor2C<"Validation", E> = { ... }

What a T suffix means, e.g. sequenceT

in sequenceT means Tuple, I borrowed the name from the corresponding Haskell function

However usually it means Transformer like in “monad transformers” (e.g. OptionT, EitherT, ReaderT, StateT)

What a K suffix means, e.g. fromEitherK or chainEitherK

K means Kleisli. A Kleisli arrow is a function with the following signature

(a: A) => F<B>

where F is a type constructor.

Example

Let’s say we have the following parser

import * as E from 'fp-ts/lib/Either'

function parse(s: string): E.Either<Error, number> {
  const n = parseFloat(s)
  return isNaN(n) ? E.left(new Error(`cannot decode ${JSON.stringify(s)} to number`)) : E.right(n)
}

and a value of type IOEither<Error, string>

import * as IE from 'fp-ts/lib/IOEither'

const input: IE.IOEither<Error, string> = IE.right('foo')

how can we parse input?

We could lift the Kleisli arrow parse, i.e. transform a function

(s: string) => E.Either<Error, number>

into a function

(s: string) => IE.IOEither<Error, number>

That’s what fromEitherK is all about

import { pipe } from 'fp-ts/lib/pipeable'

pipe(input, IE.chain(IE.fromEitherK(parse)))() // left(new Error('cannot decode "foo" to number'))

// or with less boilerplate
pipe(input, IE.chainEitherK(parse))() // left(new Error('cannot decode "foo" to number'))

What a W suffix means, e.g. chainW or chainEitherKW

W means Widen. Functions that end with W are able to aggregate errors into a union (for Either based data types) or environments into an intersection (for Reader based data types).

Example

import * as E from 'fp-ts/lib/Either'
import * as TE from 'fp-ts/lib/TaskEither'
import { pipe } from 'fp-ts/lib/pipeable'

declare function parseString(s: string): E.Either<string, number>
declare function fetchUser(id: number): TE.TaskEither<Error, User>

// this raises an error because: Type 'string' is not assignable to type 'Error'
const program_ = (s: string) => pipe(s, TE.fromEitherK(parseString), TE.chain(fetchUser))

// const program: (s: string) => TE.TaskEither<string | Error, User>
const program = (s: string) => pipe(s, TE.fromEitherK(parseString), TE.chainW(fetchUser))