# Technical overview ## A basic `Option` type ```ts // Option.ts // definition export class None { readonly tag: 'None' = 'None' } export class Some { readonly tag: 'Some' = 'Some' constructor(readonly value: A) {} } export type Option = None | Some // helpers export const none: Option = new None() export const some = (a: A): Option => { return new Some(a) } // a specialised map for Option const map = (f: (a: A) => B, fa: Option): Option => { switch (fa.tag) { case 'None': return fa case 'Some': return some(f(fa.value)) } } ``` Usage ```ts const double = (n: number): number => n * 2 const len = (s: string): number => s.length console.log(map(double, some(1))) // { tag: 'Some', value: 2 } console.log(map(double, none)) // { tag: 'None' } console.log(map(len, some(2))) // <= static error: Type 'number' is not assignable to type 'string' ``` ## Adding static land support TypeScript doesn't support higher kinded types ```ts interface Functor { map: (f: (a: A) => B, fa: ?) => ? } ``` but we can fake them with an interface ```ts // HKT.ts export interface HKT { _URI: F _A: A } ``` where `F` is a unique identifier representing the type constructor and `A` its type parameter. Now we can define a generic `Functor` interface ```ts // Functor.ts import { HKT } from './HKT' export interface Functor { map: (f: (a: A) => B, fa: HKT) => HKT } ``` and redefine the `Option` type ```ts // Option.ts // unique identifier export const URI = 'Option' export type URI = typeof URI export class None { readonly _URI!: URI readonly _A!: never readonly tag: 'None' = 'None' } export class Some { readonly _URI!: URI readonly _A!: A readonly tag: 'Some' = 'Some' constructor(readonly value: A) {} } export type Option = None | Some export const none: Option = new None() export const some = (a: A): Option => { return new Some(a) } const map = (f: (a: A) => B, fa: Option): Option => { switch (fa.tag) { case 'None': return fa case 'Some': return some(f(fa.value)) } } ``` Let's define an instance of `Functor` for `Option` ```ts // static land Functor instance export const option: Functor = { map } ``` There's a problem though, this code doesn't type-check with the following error ``` Type 'HKT<"Option", A>' is not assignable to type 'Option' ``` Every `Option` is a `HKT<"Option", A>` but the converse is not true. In order to fix this (we **know** that `Option = HKT<"Option", A>`) functions like `map` should accept the more general version `HKT<"Option", A>` and return the more specific version `Option` ```ts const map = (f: (a: A) => B, hfa: HKT): Option => { const fa = hfa as Option switch (fa.tag) { case 'None': return fa case 'Some': return some(f(fa.value)) } } export const option: Functor = { map // no error } ``` There's another issue though: when trying to use the instance we don't get an `Option` as a result ```ts // x: HKT<"Option", number> const x = option.map(double, some(1)) ``` we get an `HKT<"Option", number>`. We must somehow teach TypeScript that `HKT<"Option", number>` is really `Option`, or more generally that `HKT<"Option", A>` is `Option` for all `A`. We'll use a feature called [Module Augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html) for that. Let's move the `HKT` definition to its own file and add a type-level map named `URI2HKT` ```ts // HKT.ts export interface HKT { _URI: F _A: A } // type-level map, maps a URI to its corresponding type export interface URI2HKT {} ``` Let's add some helpers types ```ts // all URIs export type URIS = keyof URI2HKT // given a URI and a type, extracts the corresponding type export type Type = URI2HKT[URI] ``` Adding an entry to the type-level map `URI2HKT` means to leverage the module augmentation feature ```ts // Option.ts declare module './HKT' { interface URI2HKT { Option: Option // maps the type literal "Option" to the type `Option` } } ``` Now we can redefine `Functor` in order to leverage this type-level machinery ```ts // Functor.ts import { URIS, Type } from './HKT' export interface Functor1 { map: (f: (a: A) => B, fa: Type) => Type } ``` and fix the instance definition ```ts // Option.ts import { Functor1 } from './Functor' const map = (f: (a: A) => B, fa: Option): Option => { switch (fa.tag) { case 'None': return fa case 'Some': return some(f(fa.value)) } } export const option: Functor1 = { map } // x: Option const x = option.map(double, some(1)) ``` ## Adding fantasy land support Let's add a `map` method to `None` and `Some` ```ts // Option.ts export class None { readonly _URI!: URI readonly _A!: never readonly tag: 'None' = 'None' map(f: (a: A) => B): Option { return none } } export class Some { readonly _URI!: URI readonly _A!: A readonly tag: 'Some' = 'Some' constructor(readonly value: A) {} map(f: (a: A) => B): Option { return some(f(this.value)) } } export type Option = None | Some ``` Note that `None` has a type parameter now, because the signature of `map` (the method) must be the same for both `None` and `Some` otherwise TypeScript will complain. The implementation of `map` (the static function) is now trivial. ```ts const map = (f: (a: A) => B, fa: Option) => { return fa.map(f) } ``` We can now use a nice chainable API (a kind of do notation) ```ts // x: Option const x = some('foo') .map(len) .map(double) ```