Migrate from PureScript/Haskell

This guide shows you how to use fp-ts concepts if you have prior experience with PureScript or Haskell.


Do notation

PureScript

do
  print "foo"
  print "bar"
  x <- readLine
  print x

TypeScript

import { pipe } from 'fp-ts/function'
import * as T from 'fp-ts/Task'

declare const print: (s: string) => T.Task<void>
declare const readLine: T.Task<string>

pipe(
  T.Do,
  T.tap(() => print('foo')),
  T.tap(() => print('bar')),
  T.bind('x', () => readLine),
  T.flatMap(({ x }) => print(x))
)

Data

PureScript

--   ↓-- type
data Foo = Bar String | Baz Boolean
--         ↑------------↑-- constructors

TypeScript

interface Bar {
  readonly _tag: 'Bar'
  readonly value: string
}

interface Baz {
  readonly _tag: 'Baz'
  readonly value: boolean
}

// type
type Foo = Bar | Baz

// constructors
const Bar = (value: string): Foo => ({ _tag: 'Bar', value })

const Baz = (value: boolean): Foo => ({ _tag: 'Baz', value })

Polymorphic data

PureScript

data Option a = None | Some a

TypeScript

export const URI = 'Option'

export type URI = typeof URI

declare module 'fp-ts/HKT' {
  interface URItoKind<A> {
    readonly [URI]: Option<A>
  }
}

export interface None {
  readonly _tag: 'None'
}

export interface Some<A> {
  readonly _tag: 'Some'
  readonly value: A
}

export type Option<A> = None | Some<A>

export const none: Option<never> = { _tag: 'None' }

export const some = <A>(a: A): Option<A> => ({ _tag: 'Some', value: a })

Pattern matching

PureScript

maybe :: forall a b. b -> (a -> b) -> Option a -> b
maybe b _ None = b
maybe _ f (Some a) = f a

TypeScript

// here TypeScript also provides exhaustiveness check
const maybe =
  <A, B>(onNone: () => B, onSome: (a: A) => B) =>
  (fa: Option<A>): B => {
    switch (fa._tag) {
      case 'None':
        return onNone()
      case 'Some':
        return onSome(fa.value)
    }
  }

Type classes

PureScript

class Functor f where
  map :: forall a b. (a -> b) -> f a -> f b

TypeScript

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

export interface Functor1<F extends URIS> {
  readonly URI: F
  readonly map: <A, B>(fa: Kind<F, A>, f: (a: A) => B) => Kind<F, B>
}

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

// etc...

Instances

PureScript

instance functorOption :: Functor Option where
  map fn (Some x) = Some (fn x)
  map _  _        = None

TypeScript

import { Functor1 } from 'fp-ts/Functor'
import { pipe } from 'fp-ts/function'

const functorOption: Functor1<URI> = {
  URI,
  map: (fa, f) =>
    pipe(
      fa,
      maybe(
        () => none,
        (a) => some(f(a))
      )
    )
}

Type constraints

PureScript

instance semigroupOption :: Semigroup a => Semigroup (Option a) where
  append None y = y
  append x None = x
  append (Some x) (Some y) = Some (x <> y)

instance monoidOption :: Semigroup a => Monoid (Option a) where
  mempty = None

TypeScript

import { Semigroup } from 'fp-ts/Semigroup'
import { Monoid } from 'fp-ts/Monoid'

//                    ↓ the constraint is implemented as an additional parameter
function getMonoid<A>(S: Semigroup<A>): Monoid<Option<A>> {
  return {
    concat: (x, y) => {
      if (x._tag === 'Some' && y._tag === 'Some') {
        return some(S.concat(x.value, y.value))
      } else if (x._tag === 'Some') {
        return y
      } else {
        return x
      }
    },
    empty: none
  }
}

Where’s my f <$> fa <*> fb?

A few options:

import * as T from 'fp-ts/Task'
import { pipe } from 'fp-ts/function'

declare const fa: T.Task<number>
declare const fb: T.Task<string>
declare const f: (a: number) => (b: string) => boolean

const result1 = pipe(fa, T.map(f), T.ap(fb))

// ..or..
const result2 = pipe(T.of(f), T.ap(fa), T.ap(fb))