Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

version 2.5 #1133

Merged
merged 14 commits into from Feb 13, 2020
27 changes: 27 additions & 0 deletions CHANGELOG.md
Expand Up @@ -14,6 +14,33 @@
**Note**: Gaps between patch versions are faulty/broken releases. **Note**: A feature tagged as Experimental is in a
high state of flux, you're at risk of it changing without notice.

# 2.5.0

- **New Feature**
- add overloadings to `zip`, `zipWith` and `unzip`, closes #1109 (@gcanti)
- add `eqStrict`, closes #965 (@gcanti)
- `Eq`
- add `eqStrict`, closes #965 (@gcanti)
- `NonEmptyArray`
- add `fold` (@vicrac)
- `Semigroup`
- add `getIntercalateSemigroup` (@gcanti)
- `Set`
- add `toggle` (@ryota-ka)
- `TaskEither`
- add `tryCatchK` (@DenisFrezzato)
- `ReaderTaskEither`
- add missing `leftReaderTask`, `rightReaderTask` functions (@gcanti)
- `StateReaderTaskEither`
- add missing `Bifunctor`, `Alt` instances (@gcanti)
- **Experimental**
- add `ReadonlyArray` module (@gcanti)
- add `ReadonlyNonEmptyArray` module (@gcanti)
- add `ReadonlySet` module (@gcanti)
- add `ReadonlyMap` module (@gcanti)
- add `ReadonlyRecord` module (@gcanti)
- add `ReadonlyTuple` module (@gcanti)

# 2.4.4

- **Polish**
Expand Down
219 changes: 219 additions & 0 deletions docs/getting-started/Functor.md
@@ -0,0 +1,219 @@
---
title: Functor
parent: Getting started
nav_order: 6
---

# Getting started with fp-ts: Functor

In the [last post](./Category.md) about categories I presented the _TS_ category (the TypeScript category) and the central problem with function composition

> How can we compose two generic functions `f: (a: A) => B` and `g: (c: C) => D`?

Why finding solutions to this problem is so important?

Because if categories can be used to model programmming languages, morphisms (i.e. functions in _TS_) can be used to model **programs**.

Therefore solving the problem also means to find **how to compose programs in a general way**. And _this_ is pretty interesting for a developer, isn't it?

## Functions as programs

We call **pure program** a function with the following signature

```scala
(a: A) => B
```

Such a signature models a program which accepts an input of type `A` and yields a result of type `B`, without any effect.

We call **effectful program** a function with the following signature

```scala
(a: A) => F<B>
```

Such a signature models a program which accepts an input of type `A` and yields a result of type `B`, along with an **effect** `F`, where `F` is some type constructor.

Recall that a [type constructor](https://en.wikipedia.org/wiki/Type_constructor) is an `n`-ary type operator taking as argument zero or more types, and returning another type.

**Example**

Given the concrete type `string`, the `Array` type constructor returns the concrete type `Array<string>`

Here we are interested in `n`-ary type constructors with `n >= 1`, for example

| Type constructor | Effect (interpretation) |
| ---------------- | ------------------------------- |
| `Array<A>` | a non deterministic computation |
| `Option<A>` | a computation that may fail |
| `Task<A>` | an asynchronous computation |

Now back to our main problem

> How can we compose two generic functions `f: (a: A) => B` and `g: (c: C) => D`?

Since the general problem is intractable, we need to put some _constraint_ on `B` and `C`.

We already know that if `B = C` then the solution is the usual function composition

```ts
function compose<A, B, C>(g: (b: B) => C, f: (a: A) => B): (a: A) => C {
return a => g(f(a))
}
```

What about the other cases?

## In which the constraint `B = F<C>` leads to functors

Let's consider the following constraint: `B = F<C>` for some type constructor `F`, or in other words (and after some renaming)

- `f: (a: A) => F<B>` is an effectful program
- `g: (b: B) => C` is a pure program

In order to compose `f` with `g` we could find a way to **lift** `g` from a function `(b: B) => C` to a function `(fb: F<B>) => F<C>` so that we can use the usual function composition (the output type of `f` would be the same as the input type of the lifted function)

So we turned the original problem into another one: can we find such a `lift` function?

Let's see some examples

**Example** (`F = Array`)

```ts
function lift<B, C>(g: (b: B) => C): (fb: Array<B>) => Array<C> {
return fb => fb.map(g)
}
```

**Example** (`F = Option`)

```ts
import { Option, isNone, none, some } from 'fp-ts/lib/Option'

function lift<B, C>(g: (b: B) => C): (fb: Option<B>) => Option<C> {
return fb => (isNone(fb) ? none : some(g(fb.value)))
}
```

**Example** (`F = Task`)

```ts
import { Task } from 'fp-ts/lib/Task'

function lift<B, C>(g: (b: B) => C): (fb: Task<B>) => Task<C> {
return fb => () => fb().then(g)
}
```

All those `lift` functions almost look the same. It's not a coincidence, there's a functional pattern under the hood.

Indeed all those type constructors (and many others) admit a **functor instance**.

## Functors

Functors are **mappings between categories** that preserve the categorical structure, i.e. that preserve identity morphisms and composition.

Since categories are constituted of two things (objects and morphisms) a functor is constituted of two things as well:

- a **mapping between objects** that associates to each object `X` in _C_ an object in _D_
- a **mapping between morphisms** that associates to each morphism in _C_ a morphism in _D_

where _C_ and _D_ are two categories (aka two programming languages).

<img src="./images/Functor.jpg" width="300" alt="functor" />
<center>(source: [functor on ncatlab.org](https://ncatlab.org/nlab/show/functor))</center>

Even if a mapping between two different programming languages is an intriguing idea, we are more interested in a mapping where _C_ and _D_ coincide (with _TS_). In this case we talk about **endofunctors** ("endo" means "within", "inside").

From now on when I write "functor" I actually mean an endofunctor in _TS_.

### Definition

A functor is a pair `(F, lift)` where

- `F` is a `n`-ary type constructor (`n >= 1`) which maps each type `X` to the type `F<X>` (**mapping between objects**)
- `lift` is a function with the following signature

```ts
lift: <A, B>(f: (a: A) => B) => ((fa: F<A>) => F<B>)
```

which maps each function `f: (a: A) => B` to a function `lift(f): (fa: F<A>) => F<B>` (**mapping between morphisms**).

The following properties must hold

- `lift(identity`<sub>X</sub>`)` = `identity`<sub>F(X)</sub> (**identities map to identities**)
- `lift(g ∘ f) = lift(g) ∘ lift(f)` (**mapping a composition is the composition of the mappings**)

The `lift` function is also known through a variant called `map`, which is basically `lift` with the arguments rearranged

```ts
lift: <A, B>(f: (a: A) => B) => ((fa: F<A>) => F<B>)
map: <A, B>(fa: F<A>, f: (a: A) => B) => F<B>
```

Note that `map` can be derived from `lift` (and viceversa).

## Functors in `fp-ts`

How can we define a functor instance in `fp-ts`? Let's see a practical example.

The following declaration defines a model for the response of an API call

```ts
interface Response<A> {
url: string
status: number
headers: Record<string, string>
body: A
}
```

Note that the `body` field is parametrized, this makes `Response` a good candidate for a functor instance since `Response` is a `n`-ary type constructors with `n >= 1` (a necessary precondition).

In order to define a functor instance for `Response` we must define a `map` function (along with some [technicalities](../recipes/HKT.md) required by `fp-ts`)

```ts
// `Response.ts` module

import { Functor1 } from 'fp-ts/lib/Functor'

export const URI = 'Response'

export type URI = typeof URI

declare module 'fp-ts/lib/HKT' {
interface URItoKind<A> {
readonly Response: Response<A>
}
}

export interface Response<A> {
url: string
status: number
headers: Record<string, string>
body: A
}

function map<A, B>(fa: Response<A>, f: (a: A) => B): Response<B> {
return { ...fa, body: f(fa.body) }
}

// functor instance for `Response`
export const functorResponse: Functor1<URI> = {
URI,
map
}
```

## Is the general problem solved?

Not at all. Functors allow us to compose an effectful program `f` with a pure program `g`, but `g` must be **unary**, that is it must accept only one argument as input. What if `g` accepts two arguments? Or three?

| Program f | Program g | Composition |
| --------- | ----------------------- | ------------- |
| pure | pure | `g ∘ f` |
| effectful | pure (unary) | `lift(g) ∘ f` |
| effectful | pure (`n`-ary, `n > 1`) | ? |

In order to handle such circumstances we need something more: in the next [post](./Applicative.md) I'll talk about another remarkable abstraction of functional programming: **applicative functors**.
6 changes: 3 additions & 3 deletions docs/guides/HKT.md
Expand Up @@ -29,7 +29,7 @@ export type URI = typeof URI

declare module 'fp-ts/lib/HKT' {
interface URItoKind<A> {
Identity: Identity<A>
readonly Identity: Identity<A>
}
}

Expand Down Expand Up @@ -68,7 +68,7 @@ export interface URItoKind<A> {}

declare module 'fp-ts/lib/HKT' {
interface URItoKind<A> {
Identity: Identity<A> // maps the key "Identity" to the type `Identity`
readonly Identity: Identity<A> // maps the key "Identity" to the type `Identity`
}
}
```
Expand All @@ -95,7 +95,7 @@ export type URI = typeof URI

declare module 'fp-ts/lib/HKT' {
interface URItoKind2<E, A> {
Either: Either<E, A>
readonly Either: Either<E, A>
}
}

Expand Down
10 changes: 5 additions & 5 deletions docs/guides/purescript.md
Expand Up @@ -54,12 +54,12 @@ TypeScript

```ts
interface Bar {
type: 'Bar'
value: string
readonly type: 'Bar'
readonly value: string
}
interface Baz {
type: 'Baz'
value: boolean
readonly type: 'Baz'
readonly value: boolean
}
// type
type Foo = Bar | Baz
Expand All @@ -81,7 +81,7 @@ TypeScript
```ts
declare module 'fp-ts/lib/HKT' {
interface URItoKind<A> {
Option: Option<A>
readonly Option: Option<A>
}
}

Expand Down
14 changes: 7 additions & 7 deletions docs/modules/Apply.ts.md
Expand Up @@ -199,37 +199,37 @@ Tuple sequencing, i.e., take a tuple of monadic actions and does them from left-
export function sequenceT<F extends URIS4>(
F: Apply4<F>
): <S, R, E, T extends Array<Kind4<F, S, R, E, any>>>(
...t: T & { 0: Kind4<F, S, R, E, any> }
...t: T & { readonly 0: Kind4<F, S, R, E, any> }
) => Kind4<F, S, R, E, { [K in keyof T]: [T[K]] extends [Kind4<F, S, R, E, infer A>] ? A : never }>
export function sequenceT<F extends URIS3>(
F: Apply3<F>
): <R, E, T extends Array<Kind3<F, R, E, any>>>(
...t: T & { 0: Kind3<F, R, E, any> }
...t: T & { readonly 0: Kind3<F, R, E, any> }
) => Kind3<F, R, E, { [K in keyof T]: [T[K]] extends [Kind3<F, R, E, infer A>] ? A : never }>
export function sequenceT<F extends URIS3, E>(
F: Apply3C<F, E>
): <R, T extends Array<Kind3<F, R, E, any>>>(
...t: T & { 0: Kind3<F, R, E, any> }
...t: T & { readonly 0: Kind3<F, R, E, any> }
) => Kind3<F, R, E, { [K in keyof T]: [T[K]] extends [Kind3<F, R, E, infer A>] ? A : never }>
export function sequenceT<F extends URIS2>(
F: Apply2<F>
): <E, T extends Array<Kind2<F, E, any>>>(
...t: T & { 0: Kind2<F, E, any> }
...t: T & { readonly 0: Kind2<F, E, any> }
) => Kind2<F, E, { [K in keyof T]: [T[K]] extends [Kind2<F, E, infer A>] ? A : never }>
export function sequenceT<F extends URIS2, E>(
F: Apply2C<F, E>
): <T extends Array<Kind2<F, E, any>>>(
...t: T & { 0: Kind2<F, E, any> }
...t: T & { readonly 0: Kind2<F, E, any> }
) => Kind2<F, E, { [K in keyof T]: [T[K]] extends [Kind2<F, E, infer A>] ? A : never }>
export function sequenceT<F extends URIS>(
F: Apply1<F>
): <T extends Array<Kind<F, any>>>(
...t: T & { 0: Kind<F, any> }
...t: T & { readonly 0: Kind<F, any> }
) => Kind<F, { [K in keyof T]: [T[K]] extends [Kind<F, infer A>] ? A : never }>
export function sequenceT<F>(
F: Apply<F>
): <T extends Array<HKT<F, any>>>(
...t: T & { 0: HKT<F, any> }
...t: T & { readonly 0: HKT<F, any> }
) => HKT<F, { [K in keyof T]: [T[K]] extends [HKT<F, infer A>] ? A : never }> { ... }
```

Expand Down