Code Conventions
Table of contents
Import Statements
To properly import modules from fp-ts, you should use the following syntax:
import ... from 'fp-ts/<module>'
For instance, when importing the Option module, you can use the following code:
import * as Option from 'fp-ts/Option'
This ensures that you’re importing the required modules correctly.
Module structure
In general a module containing the definition of a data structure has the following structure
URIdefinition 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
Cmeans 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 an E suffix means, e.g. matchE
E means Effect. An example of its use is in the matchE destructor on monad transformers like TaskOption or ReaderTaskEither.
Example
Both of these destructions result in a Task<number>, but in the case of matchE an effect (in this case in the form of a Task) is returned on match.
import * as T from 'fp-ts/Task'
import * as TO from 'fp-ts/TaskOption'
import { pipe } from 'fp-ts/function'
const value = TO.of('hello')
// T.Task<number>
pipe(
value,
TO.match(
() => 0,
(str) => str.length
)
)
// T.Task<number>
pipe(
value,
TO.matchE(
() => T.of(0),
(str) => T.of(str.length)
)
)
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/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/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/function'
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 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 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/Either'
import * as TE from 'fp-ts/TaskEither'
import { pipe } from 'fp-ts/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))