funfix-types
Version:
Sub-package of Funfix defining type classes inspired by Haskell's standard library
927 lines (855 loc) • 36.4 kB
text/typescript
/*!
* Copyright (c) 2017 by The Funfix Project Developers.
* Some rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Either, Right, Left, applyMixins } from "funfix-core"
import { HK, Equiv, Constructor, getTypeClassInstance } from "./kinds"
import {
Apply,
ApplyLaws,
Applicative,
ApplicativeLaws,
ApplicativeError,
ApplicativeErrorLaws
} from "./applicative"
/**
* The `FlatMap` type class is a lightweight {@link Monad}.
*
* It exposes [flatMap]{@link FlatMap.flatMap}, which allows to have a
* value in a context (`F<A>`) and then feed that into a function that
* takes a normal value and returns a value in a context
* (`A => F<B>`).
*
* One motivation for separating this out from `Monad` is that there are
* situations where we can implement `flatMap` but not `pure`. For example,
* we can implement `map` or `flatMap` that transforms the values of a
* `Map<K, ?>` type, but we can't implement `pure` (because we wouldn't
* know what key to use when instantiating the new `Map`).
*
* Must obey the laws defined in {@link FlatMapLaws}.
*
* Note that having an `Monad` instance implies
* {@link Functor} and {@link Apply} implementations are also available,
* as `FlatMap` is a subtype of these.
*
* ## Implementation notes
*
* Even though in TypeScript the Funfix library is using `abstract class` to
* express type classes, when implementing this type class it is recommended
* that you implement it as a mixin using "`implements`", instead of extending
* it directly with "`extends`". See
* [TypeScript: Mixins]{@link https://www.typescriptlang.org/docs/handbook/mixins.html}
* for details and note that we already have `applyMixins` defined.
*
* Implementation example:
*
* ```typescript
* import {
* HK, FlatMap, Either,
* registerTypeClassInstance,
* applyMixins
* } from "../src/funfix"
*
* // Type alias defined for readability.
* // HK is our encoding for higher-kinded types.
* type BoxK<T> = HK<Box<any>, T>
*
* class Box<T> implements HK<Box<any>, T> {
* constructor(public value: T) {}
*
* // Implements HK<Box<any>, A>, not really needed, but useful in order
* // to avoid type casts. Note they can and should be undefined:
* readonly _funKindF: Box<any>
* readonly _funKindA: T
* }
*
* class BoxFlatMap implements FlatMap<Box<any>> {
* map<A, B>(fa: BoxK<A>, f: (a: A) => B): Box<B> {
* return new Box(f((fa as Box<A>).value))
* }
*
* flatMap<A, B>(fa: BoxK<A>, f: (a: A) => BoxK<B>): Box<B> {
* return f((fa as Box<A>).value) as Box<B>
* }
*
* tailRecM<A, B>(a: A, f: (a: A) => BoxK<Either<A, B>>): Box<B> {
* let cursor = a
* while (true) {
* const box = f(cursor) as Box<Either<A, B>>
* const v = box.value
* if (v.isRight()) return new Box(v.get())
* cursor = v.swap().get()
* }
* }
*
* // Mixed-in, as these have default implementations
* map2: <A, B, Z>(fa: BoxK<A>, fb: BoxK<B>, f: (a: A, b: B) => Z) => Box<Z>
* ap: <A, B>(fa: BoxK<A>, ff: BoxK<(a: A) => B>) => Box<B>
* product: <A, B> (fa: BoxK<A>, fb: BoxK<B>) => Box<[A, B]>
* unit: () => Box<void>
* followedBy: <A, B>(fa: BoxK<A>, fb: BoxK<B>) => Box<B>
* followedByL: <A, B>(fa: BoxK<A>, fb: () => BoxK<B>) => Box<B>
* forEffect: <A, B>(fa: BoxK<A>, fb: BoxK<B>) => Box<A>
* forEffectL: <A, B>(fa: BoxK<A>, fb: () => BoxK<B>) => Box<A>
* }
*
* // Call needed in order to implement `map`, `map2`, `product`, etc.
* // using the default implementations defined by `FlatMap`, because
* // we are using `implements` instead of `extends` above and
* // because in this sample we want the default implementations,
* // but note that you can always provide your own
* applyMixins(BoxFlatMap, [FlatMap])
*
* // Registering global Functor instance for Box, needed in order
* // for the `functorOf(Box)`, `applyOf(Box)`, `applicativeOf(Box)`
* // and `flatMapOf(Box)` calls to work
* registerTypeClassInstance(FlatMap)(Box, new BoxFunctor())
* ```
*
* We are using `implements` in order to support multiple inheritance and to
* avoid inheriting any `static` members. In the Flow definitions (e.g.
* `.js.flow` files) for Funfix these type classes are defined with
* "`interface`", as they are meant to be interfaces that sometimes have
* default implementations and not classes.
*
* ## Credits
*
* This type class is inspired by the equivalent in Haskell's
* standard library and the implementation is inspired by the
* [Typelevel Cats]{@link http://typelevel.org/cats/} project.
*/
export abstract class FlatMap<F> implements Apply<F> {
abstract flatMap<A, B>(fa: HK<F, A>, f: (a: A) => HK<F, B>): HK<F, B>
/** Inherited from {@link Functor.map}. */
abstract map<A, B>(fa: HK<F, A>, f: (a: A) => B): HK<F, B>
/**
* Keeps calling `f` until a `Right(b)` is returned.
*
* Based on Phil Freeman's
* [Stack Safety for Free]{@link http://functorial.com/stack-safety-for-free/index.pdf}.
*
* Implementations of this method should use constant stack space relative to `f`.
*/
abstract tailRecM<A, B>(a: A, f: (a: A) => HK<F, Either<A, B>>): HK<F, B>
/**
* Sequentially compose two actions, discarding any value produced
* by the first.
*
* See [followedByL]{@link FlatMap.followedByL} for a lazy version.
*/
followedBy<A, B>(fa: HK<F, A>, fb: HK<F, B>): HK<F, B> {
return this.flatMap(fa, _ => fb)
}
/**
* Sequentially compose two actions, discarding any value produced
* by the first.
*
* See [followedBy]{@link FlatMap.followedBy} for the strict version.
*/
followedByL<A, B>(fa: HK<F, A>, fb: () => HK<F, B>): HK<F, B> {
return this.flatMap(fa, _ => fb())
}
/**
* Sequentially compose two actions, discarding any value
* produced by the second.
*
* See [forEffectL]{@link FlatMap.forEffectL} for the lazy version.
*/
forEffect<A, B>(fa: HK<F, A>, fb: HK<F, B>): HK<F, A> {
return this.flatMap(fa, a => this.map(fb, _ => a))
}
/**
* Sequentially compose two actions, discarding any value
* produced by the second.
*
* See [forEffect]{@link FlatMap.forEffect} for the strict version.
*/
forEffectL<A, B>(fa: HK<F, A>, fb: () => HK<F, B>): HK<F, A> {
return this.flatMap(fa, a => this.map(fb(), _ => a))
}
/** Inherited from {@link Apply.ap}. */
ap<A, B>(fa: HK<F, A>, ff: HK<F, (a: A) => B>): HK<F, B> {
return this.flatMap(fa, a => this.map(ff, f => f(a)))
}
/** Inherited from {@link Apply.map2}. */
map2<A, B, Z>(fa: HK<F, A>, fb: HK<F, B>, f: (a: A, b: B) => Z): HK<F, Z> {
return this.flatMap(fa, a => this.map(fb, b => f(a, b)))
}
/** Inherited from {@link Apply.product}. */
product<A, B>(fa: HK<F, A>, fb: HK<F, B>): HK<F, [A, B]> {
return this.flatMap(fa, a => this.map(fb, b => [a, b] as [A, B]))
}
// Implements TypeClass<F>
/** @hidden */
static readonly _funTypeId: string = "flatMap"
/** @hidden */
static readonly _funSupertypeIds: string[] = ["functor", "apply"]
/** @hidden */
static readonly _funErasure: FlatMap<any>
}
/**
* Type class laws defined for {@link FlatMap}.
*
* This is an abstract definition. In order to use it in unit testing,
* the implementor must think of a strategy to evaluate the truthiness
* of the returned `Equiv` values.
*
* Even though in TypeScript the Funfix library is using classes to
* express these laws, when implementing this class it is recommended
* that you implement it as a mixin using `implements`, instead of extending
* it directly with `extends`. See
* [TypeScript: Mixins]{@link https://www.typescriptlang.org/docs/handbook/mixins.html}
* for details and note that we already have `applyMixins` defined.
*
* We are doing this in order to support multiple inheritance and to
* avoid inheriting any `static` members. In the Flow definitions (e.g.
* `.js.flow` files) for Funfix these classes are defined with
* `interface`, as they are meant to be interfaces that sometimes have
* default implementations and not classes.
*/
export abstract class FlatMapLaws<F> implements ApplyLaws<F> {
/**
* The {@link Apply} designated instance for `F`,
* to be tested.
*/
public readonly F: FlatMap<F>
/**
* ```typescript
* fa.flatMap(f).flatMap(g) <-> fa.flatMap(a => f(a).flatMap(g))
* ```
*/
flatMapAssociativity<A, B, C>(fa: HK<F, A>, f: (a: A) => HK<F, B>, g: (b: B) => HK<F, C>): Equiv<HK<F, C>> {
const F = this.F
return Equiv.of(
F.flatMap(F.flatMap(fa, f), g),
F.flatMap(fa, a => F.flatMap(f(a), g))
)
}
/**
* ```typescript
* fab.ap(fa) <-> fab.flatMap(f => fa.map(f))
* ```
*/
flatMapConsistentApply<A, B>(fa: HK<F, A>, fab: HK<F, (a: A) => B>): Equiv<HK<F, B>> {
const F = this.F
return Equiv.of(
F.ap(fa, fab),
F.flatMap(fab, f => F.map(fa, f))
)
}
/**
* ```typescript
* fa.followedBy(fb) <-> fa.flatMap(_ => fb)
* ```
*/
followedByConsistency<A, B>(fa: HK<F, A>, fb: HK<F, B>): Equiv<HK<F, B>> {
const F = this.F
return Equiv.of(
F.followedBy(fa, fb),
F.flatMap(fa, _ => fb)
)
}
/**
* ```typescript
* fa.followedBy(() => fb) <-> fa.flatMap(_ => fb)
* ```
*/
followedByLConsistency<A, B>(fa: HK<F, A>, fb: HK<F, B>): Equiv<HK<F, B>> {
const F = this.F
return Equiv.of(
F.followedByL(fa, () => fb),
F.flatMap(fa, _ => fb)
)
}
/**
* ```typescript
* fa.forEffect(fb) <-> fa.flatMap(a => fb.map(_ => a))
* ```
*/
forEffectConsistency<A, B>(fa: HK<F, A>, fb: HK<F, B>): Equiv<HK<F, A>> {
const F = this.F
return Equiv.of(
F.forEffect(fa, fb),
F.flatMap(fa, a => F.map(fb, _ => a))
)
}
/**
* ```typescript
* fa.forEffectL(() => fb) <-> fa.flatMap(a => fb.map(_ => a))
* ```
*/
forEffectLConsistency<A, B>(fa: HK<F, A>, fb: HK<F, B>): Equiv<HK<F, A>> {
const F = this.F
return Equiv.of(
F.forEffectL(fa, () => fb),
F.flatMap(fa, a => F.map(fb, _ => a))
)
}
tailRecMConsistentFlatMap<A>(a: A, f: (a: A) => HK<F, A>): Equiv<HK<F, A>> {
const F = this.F
const bounce = (n: number) => {
return F.tailRecM([a, n] as [A, number], x => {
const [a0, i] = x
return i > 0
? F.map(f(a0), a1 => Left([a1, i - 1] as [A, number]))
: F.map(f(a0), Right)
})
}
/*
* The law is for n >= 1
* bounce(n) == bounce(n - 1).flatMap(f)
*
* Many monads blow up if n gets too large here (for instance List, becomes
* multiplicative, so the memory is exponential in n).
*/
return Equiv.of(bounce(1), F.flatMap(bounce(0), f))
}
/** Mixed-in from {@link FunctorLaws.covariantIdentity}. */
covariantIdentity: <A>(fa: HK<F, A>) => Equiv<HK<F, A>>
/** Mixed-in from {@link FunctorLaws.covariantComposition}. */
covariantComposition: <A, B, C>(fa: HK<F, A>, f: (a: A) => B, g: (b: B) => C) => Equiv<HK<F, C>>
/** Mixed in from {@link ApplyLaws.applyComposition}. */
applyComposition: <A, B, C>(fa: HK<F, A>, fab: HK<F, (a: A) => B>, fbc: HK<F, (b: B) => C>) => Equiv<HK<F, C>>
/** Mixed in from {@link ApplyLaws.applyProductConsistency}. */
applyProductConsistency: <A, B>(fa: HK<F, A>, f: HK<F, (a: A) => B>) => Equiv<HK<F, B>>
/** Mixed in from {@link ApplyLaws.applyMap2Consistency}. */
applyMap2Consistency: <A, B>(fa: HK<F, A>, f: HK<F, (a: A) => B>) => Equiv<HK<F, B>>
}
applyMixins(FlatMapLaws, [ApplyLaws])
/**
* Given a {@link Constructor} reference, returns its associated
* {@link FlatMap} instance if it exists, or throws a `NotImplementedError`
* in case there's no such association.
*
* ```typescript
* import { Option, FlatMap, flatMapOf } from "funfix"
*
* const F: FlatMap<Option<any>> = flatMapOf(Option)
* ```
*/
export const flatMapOf: <F>(c: Constructor<F>) => FlatMap<F> =
getTypeClassInstance(FlatMap)
/**
* Given an {@link FlatMap} instance, returns the {@link FlatMapLaws}
* associated with it.
*/
export function flatMapLawsOf<F>(instance: FlatMap<F>): FlatMapLaws<F> {
return new (class extends FlatMapLaws<F> { public readonly F = instance })()
}
/**
* The `Monad` type class.
*
* Allows composition of dependent effectful functions.
*
* A `Monad` instance is defined by two operations:
*
* - `pure` from {@link Applicative}, which lifts an `A` value
* in the `F<A>` context
* - `flatMap`, which allows us to have a value in a context (`F<A>`)
* and then feed that into a function that takes a normal value and
* returns a value in a context (`A => F<B>`)
*
* See [Monads for functional programming]{@link http://homepages.inf.ed.ac.uk/wadler/papers/marktoberdorf/baastad.pdf},
* by Philip Wadler.
*
* Must obey the laws defined in {@link MonadLaws}.
*
* Note that having an `Applicative` instance implies
* {@link Functor}, {@link Apply}, {@link Applicative} and {@link FlatMap}
* implementations are also available, as `Monad` is a subtype
* of these type classes.
*
* ## Implementation notes
*
* Even though in TypeScript the Funfix library is using `abstract class` to
* express type classes, when implementing this type class it is recommended
* that you implement it as a mixin using "`implements`", instead of extending
* it directly with "`extends`". See
* [TypeScript: Mixins]{@link https://www.typescriptlang.org/docs/handbook/mixins.html}
* for details and note that we already have `applyMixins` defined.
*
* Implementation example:
*
* ```typescript
* import {
* HK, Monad, Either,
* registerTypeClassInstance,
* applyMixins
* } from "../src/funfix"
*
* // Type alias defined for readability.
* // HK is our encoding for higher-kinded types.
* type BoxK<T> = HK<Box<any>, T>
*
* class Box<T> implements HK<Box<any>, T> {
* constructor(public value: T) {}
*
* // Implements HK<Box<any>, A>, not really needed, but useful in order
* // to avoid type casts. Note they can and should be undefined:
* readonly _funKindF: Box<any>
* readonly _funKindA: T
* }
*
* class BoxMonad implements Monad<Box<any>> {
* pure<A>(a: A): Box<A> { return new Box(a) }
*
* flatMap<A, B>(fa: BoxK<A>, f: (a: A) => BoxK<B>): Box<B> {
* return f((fa as Box<A>).value) as Box<B>
* }
*
* tailRecM<A, B>(a: A, f: (a: A) => BoxK<Either<A, B>>): Box<B> {
* let cursor = a
* while (true) {
* const box = f(cursor) as Box<Either<A, B>>
* const v = box.value
* if (v.isRight()) return new Box(v.get())
* cursor = v.swap().get()
* }
* }
*
* // Mixed-in, as these have default implementations
* map: <A, B>(fa: BoxK<A>, f: (a: A) => B) => Box<B>
* map2: <A, B, Z>(fa: BoxK<A>, fb: BoxK<B>, f: (a: A, b: B) => Z) => Box<Z>
* ap: <A, B>(fa: BoxK<A>, ff: BoxK<(a: A) => B>) => Box<B>
* product: <A, B> (fa: BoxK<A>, fb: BoxK<B>) => Box<[A, B]>
* unit: () => Box<void>
* followedBy: <A, B>(fa: BoxK<A>, fb: BoxK<B>) => Box<B>
* followedByL: <A, B>(fa: BoxK<A>, fb: () => BoxK<B>) => Box<B>
* forEffect: <A, B>(fa: BoxK<A>, fb: BoxK<B>) => Box<A>
* forEffectL: <A, B>(fa: BoxK<A>, fb: () => BoxK<B>) => Box<A>
* }
*
* // Call needed in order to implement `map`, `map2`, `product`, etc.
* // using the default implementations defined by `Monad`, because
* // we are using `implements` instead of `extends` above and
* // because in this sample we want the default implementations,
* // but note that you can always provide your own
* applyMixins(BoxMonad, [Monad])
*
* // Registering global Monad instance for Box, needed in order
* // for the `functorOf(Box)`, `applyOf(Box)`, `applicativeOf(Box)`,
* // `flatMapOf(Box)` and `monadOf(Box)` calls to work
* registerTypeClassInstance(Monad)(Box, new BoxFunctor())
* ```
*
* We are using `implements` in order to support multiple inheritance and to
* avoid inheriting any `static` members. In the Flow definitions (e.g.
* `.js.flow` files) for Funfix these type classes are defined with
* "`interface`", as they are meant to be interfaces that sometimes have
* default implementations and not classes.
*
* ## Credits
*
* This type class is inspired by the equivalent in Haskell's
* standard library and the implementation is inspired by the
* [Typelevel Cats]{@link http://typelevel.org/cats/} project.
*/
export abstract class Monad<F> implements FlatMap<F>, Applicative<F> {
/** Inherited from {@link Applicative.pure}. */
abstract pure<A>(a: A): HK<F, A>
/** Inherited from {@link FlatMap.flatMap}. */
abstract flatMap<A, B>(fa: HK<F, A>, f: (a: A) => HK<F, B>): HK<F, B>
/** Inherited from {@link FlatMap.tailRecM}. */
abstract tailRecM<A, B>(a: A, f: (a: A) => HK<F, Either<A, B>>): HK<F, B>
/** Inherited from {@link Apply.ap}. */
ap<A, B>(fa: HK<F, A>, ff: HK<F, (a: A) => B>): HK<F, B> {
return this.flatMap(fa, a => this.map(ff, f => f(a)))
}
/** Inherited from {@link Functor.map}. */
map<A, B>(fa: HK<F, A>, f: (a: A) => B): HK<F, B> {
return this.flatMap(fa, a => this.pure(f(a)))
}
/** Inherited from {@link Apply.map2}. */
map2<A, B, Z>(fa: HK<F, A>, fb: HK<F, B>, f: (a: A, b: B) => Z): HK<F, Z> {
const F = this
return F.flatMap(fa, a => F.map(fb, b => f(a, b)))
}
/** Inherited from {@link Apply.product}. */
product<A, B>(fa: HK<F, A>, fb: HK<F, B>): HK<F, [A, B]> {
const F = this
return F.flatMap(fa, a => F.map(fb, b => [a, b] as [A, B]))
}
/** Mixed-in from {@link Applicative.unit}. */
unit: () => HK<F, void>
/** Mixed-in from {@link FlatMap.followedBy}. */
followedBy: <A, B>(fa: HK<F, A>, fb: HK<F, B>) => HK<F, B>
/** Mixed-in from {@link FlatMap.followedByL}. */
followedByL: <A, B>(fa: HK<F, A>, fb: () => HK<F, B>) => HK<F, B>
/** Mixed-in from {@link FlatMap.forEffect}. */
forEffect: <A, B>(fa: HK<F, A>, fb: HK<F, B>) => HK<F, A>
/** Mixed-in from {@link FlatMap.forEffectL}. */
forEffectL: <A, B>(fa: HK<F, A>, fb: () => HK<F, B>) => HK<F, A>
// Implements TypeClass<F>
/** @hidden */
static readonly _funTypeId: string = "monad"
/** @hidden */
static readonly _funSupertypeIds: string[] = ["functor", "apply", "applicative", "flatMap"]
/** @hidden */
static readonly _funErasure: Monad<any>
}
applyMixins(Monad, [Applicative, FlatMap])
/**
* Type class laws defined for {@link Monad}.
*
* This is an abstract definition. In order to use it in unit testing,
* the implementor must think of a strategy to evaluate the truthiness
* of the returned `Equiv` values.
*
* Even though in TypeScript the Funfix library is using classes to
* express these laws, when implementing this class it is recommended
* that you implement it as a mixin using `implements`, instead of extending
* it directly with `extends`. See
* [TypeScript: Mixins]{@link https://www.typescriptlang.org/docs/handbook/mixins.html}
* for details and note that we already have `applyMixins` defined.
*
* We are doing this in order to support multiple inheritance and to
* avoid inheriting any `static` members. In the Flow definitions (e.g.
* `.js.flow` files) for Funfix these classes are defined with
* `interface`, as they are meant to be interfaces that sometimes have
* default implementations and not classes.
*/
export abstract class MonadLaws<F> implements ApplicativeLaws<F>, FlatMapLaws<F> {
/**
* The {@link Monad} designated instance for `F`,
* to be tested.
*/
public readonly F: Monad<F>
monadLeftIdentity<A, B>(a: A, f: (a: A) => HK<F, B>): Equiv<HK<F, B>> {
const F = this.F
return Equiv.of(F.flatMap(F.pure(a), f), f(a))
}
monadRightIdentity<A, B>(fa: HK<F, A>): Equiv<HK<F, A>> {
const F = this.F
return Equiv.of(F.flatMap(fa, F.pure), fa)
}
mapFlatMapCoherence<A, B>(fa: HK<F, A>, f: (a: A) => B): Equiv<HK<F, B>> {
const F = this.F
return Equiv.of(F.flatMap(fa, a => F.pure(f(a))), F.map(fa, f))
}
tailRecMStackSafety(): Equiv<HK<F, number>> {
const F = this.F
const n = 10000
const res = F.tailRecM(0, i => F.pure(i < n ? Left(i + 1) : Right(i)))
return Equiv.of(res, F.pure(n))
}
/** Mixed-in from {@link FunctorLaws.covariantIdentity}. */
covariantIdentity: <A>(fa: HK<F, A>) => Equiv<HK<F, A>>
/** Mixed-in from {@link FunctorLaws.covariantComposition}. */
covariantComposition: <A, B, C>(fa: HK<F, A>, f: (a: A) => B, g: (b: B) => C) => Equiv<HK<F, C>>
/** Mixed-in from {@link ApplyLaws.applyComposition}. */
applyComposition: <A, B, C>(fa: HK<F, A>, fab: HK<F, (a: A) => B>, fbc: HK<F, (b: B) => C>) => Equiv<HK<F, C>>
/** Mixed-in from {@link ApplyLaws.applyProductConsistency}. */
applyProductConsistency: <A, B>(fa: HK<F, A>, f: HK<F, (a: A) => B>) => Equiv<HK<F, B>>
/** Mixed-in from {@link ApplyLaws.applyMap2Consistency}. */
applyMap2Consistency: <A, B>(fa: HK<F, A>, f: HK<F, (a: A) => B>) => Equiv<HK<F, B>>
/** Mixed-in from {@link ApplicativeLaws.applyComposition}. */
applicativeIdentity: <A>(fa: HK<F, A>) => Equiv<HK<F, A>>
/** Mixed-in from {@link ApplicativeLaws.applicativeHomomorphism}. */
applicativeHomomorphism: <A, B>(a: A, f: (a: A) => B) => Equiv<HK<F, B>>
/** Mixed-in from {@link ApplicativeLaws.applicativeInterchange}. */
applicativeInterchange: <A, B>(a: A, ff: HK<F, (a: A) => B>) => Equiv<HK<F, B>>
/** Mixed-in from {@link ApplicativeLaws.applicativeMap}. */
applicativeMap: <A, B>(fa: HK<F, A>, f: (a: A) => B) => Equiv<HK<F, B>>
/** Mixed-in from {@link ApplicativeLaws.applicativeComposition}. */
applicativeComposition: <A, B, C>(fa: HK<F, A>, fab: HK<F, (a: A) => B>, fbc: HK<F, (b: B) => C>) => Equiv<HK<F, C>>
/** Mixed-in from {@link ApplicativeLaws.applicativeUnit}. */
applicativeUnit: <A>(a: A) => Equiv<HK<F, A>>
/** Mixed-in from {@link FlatMapLaws.flatMapAssociativity}. */
flatMapAssociativity: <A, B, C>(fa: HK<F, A>, f: (a: A) => HK<F, B>, g: (b: B) => HK<F, C>) => Equiv<HK<F, C>>
/** Mixed-in from {@link FlatMapLaws.flatMapConsistentApply}. */
flatMapConsistentApply: <A, B>(fa: HK<F, A>, fab: HK<F, (a: A) => B>) => Equiv<HK<F, B>>
/** Mixed-in from {@link FlatMapLaws.followedByConsistency}. */
followedByConsistency: <A, B>(fa: HK<F, A>, fb: HK<F, B>) => Equiv<HK<F, B>>
/** Mixed-in from {@link FlatMapLaws.followedByLConsistency}. */
followedByLConsistency: <A, B>(fa: HK<F, A>, fb: HK<F, B>) => Equiv<HK<F, B>>
/** Mixed-in from {@link FlatMapLaws.forEffectConsistency}. */
forEffectConsistency: <A, B>(fa: HK<F, A>, fb: HK<F, B>) => Equiv<HK<F, A>>
/** Mixed-in from {@link FlatMapLaws.forEffectLConsistency}. */
forEffectLConsistency: <A, B>(fa: HK<F, A>, fb: HK<F, B>) => Equiv<HK<F, A>>
/** Mixed-in from {@link FlatMapLaws.tailRecMConsistentFlatMap}. */
tailRecMConsistentFlatMap: <A>(a: A, f: (a: A) => HK<F, A>) => Equiv<HK<F, A>>
}
applyMixins(MonadLaws, [ApplicativeLaws, FlatMapLaws])
/**
* Given a {@link Constructor} reference, returns its associated
* {@link Monad} instance if it exists, or throws a `NotImplementedError`
* in case there's no such association.
*
* ```typescript
* import { Option, Monad, monadOf } from "funfix"
*
* const F: Monad<Option<any>> = monadOf(Option)
* ```
*/
export const monadOf: <F>(c: Constructor<F>) => Monad<F> =
getTypeClassInstance(Monad)
/**
* Given an {@link Monad} instance, returns the {@link MonadLaws}
* associated with it.
*/
export function monadLawsOf<F>(instance: Monad<F>): MonadLaws<F> {
return new (class extends MonadLaws<F> { public readonly F = instance })()
}
/**
* The `MonadError` type class is a {@link Applicative} that
* also allows you to raise and or handle an error value.
*
* This type class allows one to abstract over error-handling
* applicative types.
*
* MUST follow the law defined in {@link MonadErrorLaws}.
*
* ## Implementation notes
*
* Even though in TypeScript the Funfix library is using `abstract class` to
* express type classes, when implementing this type class it is recommended
* that you implement it as a mixin using "`implements`", instead of extending
* it directly with "`extends`". See
* [TypeScript: Mixins]{@link https://www.typescriptlang.org/docs/handbook/mixins.html}
* for details and note that we already have `applyMixins` defined.
*
* Implementation example:
*
* ```typescript
* import {
* HK,
* MonadError,
* registerTypeClassInstance,
* applyMixins,
* Try
* } from "funfix"
*
* // Type alias defined for readability.
* // HK is our encoding for higher-kinded types.
* type BoxK<T> = HK<Box<any>, T>
*
* class Box<T> implements HK<Box<any>, T> {
* constructor(public value: Try<T>) {}
*
* // Implements HK<Box<any>, A>, not really needed, but useful in order
* // to avoid type casts. Note they can and should be undefined:
* readonly _funKindF: Box<any>
* readonly _funKindA: T
* }
*
* class BoxMonadError implements MonadError<Box<any>, any> {
* pure<A>(a: A): Box<A> { return new Box(Try.success(a)) }
*
* flatMap<A, B>(fa: BoxK<A>, f: (a: A) => BoxK<B>): Box<B> {
* throw new NotImplementedError("Provide implementation")
* }
*
* tailRecM<A, B>(a: A, f: (a: A) => BoxK<Either<A, B>>): Box<B> {
* throw new NotImplementedError("Provide implementation")
* }
*
* raise<A>(e: any): HK<Box<any>, A> {
* return new Box(Try.failure(e))
* }
*
* recoverWith<A>(fa: BoxK<A>, f: (e: any) => BoxK<A>): HK<Box<any>, A> {
* return new Box((fa as Box<A>).value.recoverWith(e => (f(e) as Box<A>).value))
* }
*
* // Mixed in
* map: <A, B>(fa: BoxK<A>, f: (a: A) => B) => Box<B>
* map2: <A, B, Z>(fa: BoxK<A>, fb: BoxK<B>, f: (a: A, b: B) => Z) => Box<Z>
* ap: <A, B>(fa: BoxK<A>, ff: BoxK<(a: A) => B>) => Box<B>
* product: <A, B> (fa: BoxK<A>, fb: BoxK<B>) => Box<[A, B]>
* unit: () => Box<void>
* followedBy: <A, B>(fa: BoxK<A>, fb: BoxK<B>) => Box<B>
* followedByL: <A, B>(fa: BoxK<A>, fb: () => BoxK<B>) => Box<B>
* forEffect: <A, B>(fa: BoxK<A>, fb: BoxK<B>) => Box<A>
* forEffectL: <A, B>(fa: BoxK<A>, fb: () => BoxK<B>) => Box<A>
* recover: <A>(fa: HK<Box<any>, A>, f: (e: any) => A) => HK<Box<any>, A>
* attempt: <A>(fa: HK<Box<any>, A>) => HK<Box<any>, Either<any, A>>
* }
*
* // Call needed in order to implement `map`, `map2`, `product`, etc.
* // using the default implementations defined by `MonadError`,
* // because we are using `implements` instead of `extends` above and
* // because in this sample we want the default implementations,
* // but note that you can always provide your own
* applyMixins(BoxMonadError, [MonadError])
*
* // Registering global MonadError instance for Box, needed in order
* // for the `functorOf(Box)`, `applyOf(Box)`, `applicativeOf(Box)`
* // and `monadErrorOf(Box)` calls to work
* registerTypeClassInstance(MonadError)(Box, new BoxMonadError())
* ```
*
* We are using `implements` in order to support multiple inheritance and to
* avoid inheriting any `static` members. In the Flow definitions (e.g.
* `.js.flow` files) for Funfix these type classes are defined with
* "`interface`", as they are meant to be interfaces that sometimes have
* default implementations and not classes.
*
* ## Credits
*
* This type class is inspired by the equivalent in Haskell's
* standard library and the implementation is inspired by the
* [Typelevel Cats]{@link http://typelevel.org/cats/} project.
*/
export abstract class MonadError<F, E> implements ApplicativeError<F, E>, Monad<F> {
/** Inherited from {@link Applicative.pure}. */
abstract pure<A>(a: A): HK<F, A>
/** Inherited from {@link ApplicativeError.raise}. */
abstract raise<A>(e: E): HK<F, A>
/** Inherited from {@link FlatMap.flatMap}. */
abstract flatMap<A, B>(fa: HK<F, A>, f: (a: A) => HK<F, B>): HK<F, B>
/** Inherited from {@link FlatMap.tailRecM}. */
abstract tailRecM<A, B>(a: A, f: (a: A) => HK<F, Either<A, B>>): HK<F, B>
/** Inherited from {@link ApplicativeError.recoverWith}. */
abstract recoverWith<A>(fa: HK<F, A>, f: (e: E) => HK<F, A>): HK<F, A>
/** Mixed-in from {@link ApplicativeError.recover}. */
recover: <A>(fa: HK<F, A>, f: (e: E) => A) => HK<F, A>
/** Mixed-in from {@link ApplicativeError.attempt}. */
attempt: <A>(fa: HK<F, A>) => HK<F, Either<E, A>>
/** Mixed-in from {@link Applicative.unit}. */
unit: () => HK<F, void>
/** Mixed-in from {@link Applicative.map}. */
map: <A, B>(fa: HK<F, A>, f: (a: A) => B) => HK<F, B>
/** Mixed-in from {@link Apply.map2}. */
map2: <A, B, Z>(fa: HK<F, A>, fb: HK<F, B>, f: (a: A, b: B) => Z) => HK<F, Z>
/** Mixed-in from {@link Apply.product}. */
product: <A, B>(fa: HK<F, A>, fb: HK<F, B>) => HK<F, [A, B]>
/** Mixed-in from {@link FlatMap.followedBy}. */
followedBy: <A, B>(fa: HK<F, A>, fb: HK<F, B>) => HK<F, B>
/** Mixed-in from {@link FlatMap.followedByL}. */
followedByL: <A, B>(fa: HK<F, A>, fb: () => HK<F, B>) => HK<F, B>
/** Mixed-in from {@link FlatMap.forEffect}. */
forEffect: <A, B>(fa: HK<F, A>, fb: HK<F, B>) => HK<F, A>
/** Mixed-in from {@link FlatMap.forEffectL}. */
forEffectL: <A, B>(fa: HK<F, A>, fb: () => HK<F, B>) => HK<F, A>
/** Mixed-in from {@link Monad.ap}. */
ap: <A, B>(fa: HK<F, A>, ff: HK<F, (a: A) => B>) => HK<F, B>
// Implements TypeClass<F>
/** @hidden */
static readonly _funTypeId: string = "monadError"
/** @hidden */
static readonly _funSupertypeIds: string[] =
["functor", "apply", "applicative", "monad", "applicativeError"]
/** @hidden */
static readonly _funErasure: MonadError<any, any>
}
applyMixins(MonadError, [Monad, ApplicativeError])
/**
* Type class laws defined for {@link MonadError}.
*
* This is an abstract definition. In order to use it in unit testing,
* the implementor must think of a strategy to evaluate the truthiness
* of the returned `Equiv` values.
*
* Even though in TypeScript the Funfix library is using classes to
* express these laws, when implementing this class it is recommended
* that you implement it as a mixin using `implements`, instead of extending
* it directly with `extends`. See
* [TypeScript: Mixins]{@link https://www.typescriptlang.org/docs/handbook/mixins.html}
* for details and note that we already have `applyMixins` defined.
*
* We are doing this in order to support multiple inheritance and to
* avoid inheriting any `static` members. In the Flow definitions (e.g.
* `.js.flow` files) for Funfix these classes are defined with
* `interface`, as they are meant to be interfaces that sometimes have
* default implementations and not classes.
*/
export abstract class MonadErrorLaws<F, E> implements ApplicativeErrorLaws<F, E>, MonadLaws<F> {
/**
* The {@link MonadError} designated instance for `F`,
* to be tested.
*/
public readonly F: MonadError<F, E>
monadErrorLeftZero<A, B>(e: E, f: (a: A) => HK<F, B>): Equiv<HK<F, B>> {
const F = this.F
return Equiv.of(F.flatMap(F.raise<A>(e), f), F.raise<B>(e))
}
/** Mixed-in from {@link ApplicativeErrorLaws.applicativeErrorRecoverWith}. */
applicativeErrorRecoverWith: <A>(e: E, f: (e: E) => HK<F, A>) => Equiv<HK<F, A>>
/** Mixed-in from {@link ApplicativeErrorLaws.applicativeErrorRecover}. */
applicativeErrorRecover: <A>(e: E, f: (e: E) => A) => Equiv<HK<F, A>>
/** Mixed-in from {@link ApplicativeErrorLaws.recoverWithPure}. */
recoverWithPure: <A>(a: A, f: (e: E) => HK<F, A>) => Equiv<HK<F, A>>
/** Mixed-in from {@link ApplicativeErrorLaws.recoverPure}. */
recoverPure: <A>(a: A, f: (e: E) => A) => Equiv<HK<F, A>>
/** Mixed-in from {@link ApplicativeErrorLaws.raiseErrorAttempt}. */
raiseErrorAttempt: (e: E) => Equiv<HK<F, Either<E, void>>>
/** Mixed-in from {@link ApplicativeErrorLaws.pureAttempt}. */
pureAttempt: <A>(a: A) => Equiv<HK<F, Either<E, A>>>
/** Mixed-in from {@link MonadLaws.monadLeftIdentity}. */
monadLeftIdentity: <A, B>(a: A, f: (a: A) => HK<F, B>) => Equiv<HK<F, B>>
/** Mixed-in from {@link MonadLaws.monadRightIdentity}. */
monadRightIdentity: <A, B>(fa: HK<F, A>) => Equiv<HK<F, A>>
/** Mixed-in from {@link MonadLaws.mapFlatMapCoherence}. */
mapFlatMapCoherence: <A, B>(fa: HK<F, A>, f: (a: A) => B) => Equiv<HK<F, B>>
/** Mixed-in from {@link MonadLaws.tailRecMStackSafety}. */
tailRecMStackSafety: () => Equiv<HK<F, number>>
/** Mixed-in from {@link FlatMapLaws.flatMapAssociativity}. */
flatMapAssociativity: <A, B, C>(fa: HK<F, A>, f: (a: A) => HK<F, B>, g: (b: B) => HK<F, C>) => Equiv<HK<F, C>>
/** Mixed-in from {@link FlatMapLaws.flatMapConsistentApply}. */
flatMapConsistentApply: <A, B>(fa: HK<F, A>, fab: HK<F, (a: A) => B>) => Equiv<HK<F, B>>
/** Mixed-in from {@link FlatMapLaws.followedByConsistency}. */
followedByConsistency: <A, B>(fa: HK<F, A>, fb: HK<F, B>) => Equiv<HK<F, B>>
/** Mixed-in from {@link FlatMapLaws.followedByLConsistency}. */
followedByLConsistency: <A, B>(fa: HK<F, A>, fb: HK<F, B>) => Equiv<HK<F, B>>
/** Mixed-in from {@link FlatMapLaws.forEffectConsistency}. */
forEffectConsistency: <A, B>(fa: HK<F, A>, fb: HK<F, B>) => Equiv<HK<F, A>>
/** Mixed-in from {@link FlatMapLaws.forEffectLConsistency}. */
forEffectLConsistency: <A, B>(fa: HK<F, A>, fb: HK<F, B>) => Equiv<HK<F, A>>
/** Mixed-in from {@link FlatMapLaws.tailRecMConsistentFlatMap}. */
tailRecMConsistentFlatMap: <A>(a: A, f: (a: A) => HK<F, A>) => Equiv<HK<F, A>>
/** Mixed-in from {@link ApplicativeLaws.applicativeIdentity}. */
applicativeIdentity: <A>(fa: HK<F, A>) => Equiv<HK<F, A>>
/** Mixed-in from {@link ApplicativeLaws.applicativeHomomorphism}. */
applicativeHomomorphism: <A, B>(a: A, f: (a: A) => B) => Equiv<HK<F, B>>
/** Mixed-in from {@link ApplicativeLaws.applicativeInterchange}. */
applicativeInterchange: <A, B>(a: A, ff: HK<F, (a: A) => B>) => Equiv<HK<F, B>>
/** Mixed-in from {@link ApplicativeLaws.applicativeMap}. */
applicativeMap: <A, B>(fa: HK<F, A>, f: (a: A) => B) => Equiv<HK<F, B>>
/** Mixed-in from {@link ApplicativeLaws.applicativeComposition}. */
applicativeComposition: <A, B, C>(fa: HK<F, A>, fab: HK<F, (a: A) => B>, fbc: HK<F, (b: B) => C>) => Equiv<HK<F, C>>
/** Mixed-in from {@link ApplicativeLaws.applicativeUnit}. */
applicativeUnit: <A>(a: A) => Equiv<HK<F, A>>
/** Mixed-in from {@link FunctorLaws.covariantIdentity}. */
covariantIdentity: <A>(fa: HK<F, A>) => Equiv<HK<F, A>>
/** Mixed-in from {@link FunctorLaws.covariantComposition}. */
covariantComposition: <A, B, C>(fa: HK<F, A>, f: (a: A) => B, g: (b: B) => C) => Equiv<HK<F, C>>
/** Mixed-in from {@link ApplyLaws.applyComposition}. */
applyComposition: <A, B, C>(fa: HK<F, A>, fab: HK<F, (a: A) => B>, fbc: HK<F, (b: B) => C>) => Equiv<HK<F, C>>
/** Mixed-in from {@link ApplyLaws.applyProductConsistency}. */
applyProductConsistency: <A, B>(fa: HK<F, A>, f: HK<F, (a: A) => B>) => Equiv<HK<F, B>>
/** Mixed-in from {@link ApplyLaws.applyMap2Consistency}. */
applyMap2Consistency: <A, B>(fa: HK<F, A>, f: HK<F, (a: A) => B>) => Equiv<HK<F, B>>
}
applyMixins(MonadErrorLaws, [MonadLaws, ApplicativeErrorLaws])
/**
* Given a {@link Constructor} reference, returns its associated
* {@link MonadError} instance if it exists, or throws a `NotImplementedError`
* in case there's no such association.
*
* ```typescript
* import { IO, MonadError, monadErrorOf } from "funfix"
*
* const F: MonadError<IO<any>> = monadErrorOf(IO)
* ```
*/
export const monadErrorOf: <F, E>(c: Constructor<F>) => MonadError<F, E> =
getTypeClassInstance(MonadError)
/**
* Given an {@link MonadError} instance, returns the
* {@link MonadErrorLaws} associated with it.
*/
export function monadErrorLawsOf<F,E>(instance: MonadError<F,E>): MonadErrorLaws<F,E> {
return new (class extends MonadErrorLaws<F,E> { public readonly F = instance })()
}