Files
optolith-client/src/Data/Foldable.ts
T
Elytherion a8f326c190 Trying to decrease dependencies
Foldable and other files still too much interdependent
2019-02-11 01:04:19 +01:00

728 lines
16 KiB
TypeScript

import { pipe } from "ramda";
import { add, inc, max, min, multiply } from "../App/Utils/mathUtils";
import { not } from "../App/Utils/not";
import { Either, isEither, isLeft, isRight, Left, Right } from "./Either";
import { equals } from "./Eq";
import { ident } from "./Function";
import { append, Cons, isList, isNil, List, Nil, NonEmptyList } from "./List";
import { isJust, isMaybe, isNothing, Just, Maybe, Nothing } from "./Maybe";
import { isOrderedMap, OrderedMap } from "./OrderedMap";
import { isOrderedSet, OrderedSet } from "./OrderedSet";
import { fromBinary, Pair } from "./Pair";
import { showP } from "./Show";
export type Foldable<A> = Either<any, A>
| List<A>
| Maybe<A>
| OrderedMap<any, A>
| OrderedSet<A>
export type NonEmptyFoldable<A> = Right<A>
| NonEmptyList<A>
| Just<A>
| OrderedMap<any, A>
| OrderedSet<A>
/**
* `foldr :: (a -> b -> b) -> b -> t a -> b`
*
* Right-associative fold of a structure.
*
* In the case of lists, `foldr`, when applied to a binary operator, a
* starting value (typically the right-identity of the operator), and a list,
* reduces the list using the binary operator, from right to left:
*
* ```foldr f z [x1, x2, ..., xn] == x1 `f` (x2 `f` ... (xn `f` z)...)```
*/
export const foldr =
<A, B>
(f: (x: A) => (acc: B) => B) =>
(initial: B) =>
(x: Foldable<A>): B => {
if (isList (x)) {
return isNil (x) ? initial : f (x .x) (foldr (f) (initial) (x .xs))
}
if (isOrderedMap (x)) {
return [...x .value]
.reduceRight<B> ((acc, e) => f (e [1]) (acc), initial)
}
if (isOrderedSet (x)) {
return [...x .value] .reduceRight<B> ((acc, e) => f (e) (acc), initial)
}
if (isEither (x)) {
return isRight (x) ? f (x .value) (initial) : initial
}
if (isMaybe (x)) {
return isJust (x) ? f (x .value) (initial) : initial
}
throw new TypeError (instanceErrorMsg ("foldr") (x))
}
/**
* `foldl :: (b -> a -> b) -> b -> t a -> b`
*
* Left-associative fold of a structure.
*
* In the case of lists, foldl, when applied to a binary operator, a starting
* value (typically the left-identity of the operator), and a list, reduces
* the list using the binary operator, from left to right:
*
* ```foldl f z [x1, x2, ..., xn] == (...((z `f` x1) `f` x2) `f`...) `f` xn```
*/
export const foldl =
<A, B>
(f: (acc: B) => (x: A) => B) =>
(initial: B) =>
(x: Foldable<A>): B => {
if (isList (x)) {
return isNil (x) ? initial : foldl (f) (f (initial) (x .x)) (x .xs)
}
if (isOrderedMap (x)) {
return [...x .value] .reduce<B> ((acc, e) => f (acc) (e [1]), initial)
}
if (isOrderedSet (x)) {
return [...x .value] .reduce<B> ((acc, e) => f (acc) (e), initial)
}
if (isEither (x)) {
return isRight (x) ? f (initial) (x .value) : initial
}
if (isMaybe (x)) {
return isJust (x) ? f (initial) (x .value) : initial
}
throw new TypeError (instanceErrorMsg ("foldl") (x))
}
/**
* `foldr1 :: (a -> a -> a) -> t a -> a`
*
* A variant of `foldr` that has no base case, and thus may only be applied to
* non-empty structures.
*
* `foldr1 f = foldr1 f . toList`
*/
export const foldr1 =
<A>
(f: (x: A) => (acc: A) => A) =>
(x: NonEmptyFoldable<A>): A => {
if (isList (x)) {
if (!isNil (x)) {
return foldr1ListS (f) (x)
}
throw new TypeError (emptyErrorMsg ("foldr1") (x))
}
if (isOrderedMap (x)) {
if (x .value .size > 0) {
const arr = [...x .value]
const _init = arr .slice (0, -1)
const _last = arr [arr .length - 1]
return _init .reduceRight<A> ((acc, e) => f (e [1]) (acc), _last [1])
}
throw new TypeError (emptyErrorMsg ("foldr1") (x))
}
if (isOrderedSet (x)) {
if (x .value .size > 0) {
const arr = [...x .value]
const _init = arr .slice (0, -1)
const _last = arr [arr .length - 1]
return _init .reduceRight<A> ((acc, e) => f (e) (acc), _last)
}
throw new TypeError (emptyErrorMsg ("foldr1") (x))
}
if (isEither (x)) {
if (isRight (x)) {
return x .value
}
throw new TypeError (emptyErrorMsg ("foldr1") (x))
}
if (isMaybe (x)) {
if (isJust (x)) {
return x .value
}
throw new TypeError (emptyErrorMsg ("foldr1") (x))
}
throw new TypeError (instanceErrorMsg ("foldr1") (x))
}
const foldr1ListS =
<A> (f: (x: A) => (acc: A) => A) => (xs: NonEmptyList<A>): A =>
isNil (xs .xs)
? xs .x
: f (xs .x) (foldr1ListS (f) (xs .xs))
/**
* `foldl1 :: (a -> a -> a) -> t a -> a`
*
* A variant of `foldl` that has no base case, and thus may only be applied to
* non-empty structures.
*
* `foldl1 f = foldl1 f . toList`
*/
export const foldl1 =
<A>
(f: (acc: A) => (x: A) => A) =>
(x: NonEmptyFoldable<A>): A => {
if (isList (x)) {
if (!isNil (x)) {
return foldl1ListS (f) (x .xs) (x .x)
}
throw new TypeError (emptyErrorMsg ("foldl1") (x))
}
if (isOrderedMap (x)) {
if (x .value .size > 0) {
const [_head, ..._tail] = x
return _tail .reduce<A> ((acc, e) => f (acc) (e [1]), _head [1])
}
throw new TypeError (emptyErrorMsg ("foldl1") (x))
}
if (isOrderedSet (x)) {
if (x .value .size > 0) {
const [_head, ..._tail] = x
return _tail .reduce<A> ((acc, e) => f (acc) (e), _head)
}
throw new TypeError (emptyErrorMsg ("foldl1") (x))
}
if (isEither (x)) {
if (isRight (x)) {
return x .value
}
throw new TypeError (emptyErrorMsg ("foldl1") (x))
}
if (isMaybe (x)) {
if (isJust (x)) {
return x .value
}
throw new TypeError (emptyErrorMsg ("foldl1") (x))
}
throw new TypeError (instanceErrorMsg ("foldl1") (x))
}
const foldl1ListS =
<A>
(f: (acc: A) => (x: A) => A) =>
(xs: List<A>) =>
(acc: A): A =>
isNil (xs) ? acc : foldl1ListS (f) (xs .xs) (f (acc) (xs .x))
interface FoldableToList {
<A> (x: Either<any, A> | List<A> | Maybe<A> | OrderedSet<A>): List<A>
<K, A> (x: OrderedMap<K, A>): List<Pair<K, A>>
}
/**
* `toList :: t a -> [a]`
*
* List of elements of a structure, from left to right.
*/
export const toList: FoldableToList =
<A> (x: Foldable<A>): List<A> => {
if (isList (x)) {
return x
}
if (isOrderedMap (x)) {
return List.fromArray (
[...x .value] .map (([k, a]) => fromBinary (k, a))
) as List<A>
}
if (isOrderedSet (x)) {
return List (...x)
}
if (isEither (x)) {
return isRight (x) ? Cons (x .value, Nil) : Nil
}
if (isMaybe (x)) {
return isJust (x) ? Cons (x .value, Nil) : Nil
}
throw new TypeError (instanceErrorMsg ("toList") (x))
}
interface FoldableNull {
<A> (x: Either<A, any>): x is Left<A>
<A> (xs: List<A>): xs is Nil
<A> (x: Maybe<A>): x is Nothing
<K, A> (x: OrderedMap<K, A>): boolean
<A> (x: OrderedSet<A>): boolean
}
/**
* `null :: t a -> Bool`
*
* Test whether the structure is empty. The default implementation is optimized
* for structures that are similar to cons-lists, because there is no general
* way to do better.
*/
export const fnull: FoldableNull =
(x: Foldable<any>): x is any => {
if (isList (x)) {
return isNil (x)
}
if (isOrderedMap (x)) {
return x .value .size === 0
}
if (isOrderedSet (x)) {
return x .value .size === 0
}
if (isEither (x)) {
return isLeft (x)
}
if (isMaybe (x)) {
return isNothing (x)
}
throw new TypeError (instanceErrorMsg ("null") (x))
}
/**
* `length :: t a -> Int`
*
* Returns the size/length of a finite structure as an `Int`. The default
* implementation is optimized for structures that are similar to cons-lists,
* because there is no general way to do better.
*/
export const length =
(x: Foldable<any>): number => {
if (isList (x)) {
return foldr<any, number> (() => inc) (0) (x)
}
if (isOrderedMap (x)) {
return x .value .size
}
if (isOrderedSet (x)) {
return x .value .size
}
if (isEither (x)) {
return isRight (x) ? 1 : 0
}
if (isMaybe (x)) {
return isJust (x) ? 1 : 0
}
throw new TypeError (instanceErrorMsg ("length") (x))
}
/**
* `elem :: Eq a => a -> t a -> Bool`
*
* Does the element occur in the structure?
*/
export const elem =
<A> (e: A) => (x: Foldable<A>): boolean => {
if (isList (x)) {
return isNil (x) ? false : equals (e) (x .x) || elem (e) (x .xs)
}
if (isOrderedMap (x)) {
return [...x .value .values ()] .some (equals (e))
}
if (isOrderedSet (x)) {
return [...x .value] .some (equals (e))
}
if (isEither (x)) {
return isRight (x) && equals (e) (x .value)
}
if (isMaybe (x)) {
return isJust (x) && equals (e) (x .value)
}
throw new TypeError (instanceErrorMsg ("elem") (x))
}
/**
* `elemF :: Eq a => t a -> a -> Bool`
*
* Does the element occur in the structure?
*
* Flipped version of `elem`.
*/
export const elemF = <A> (xs: Foldable<A>) => (x: A): boolean => elem (x) (xs)
/**
* `sum :: Num a => t a -> a`
*
* The `sum` function computes the sum of the numbers of a structure.
*/
export const sum = foldr (add) (0)
/**
* `product :: Num a => t a -> a`
*
* The `product` function computes the product of the numbers of a structure.
*/
export const product = foldr (multiply) (1)
/**
* `maximum :: Ord a => t a -> a`
*
* The largest element of a non-empty structure.
*/
export const maximum = foldr (max) (-Infinity)
/**
* `minimum :: Ord a => t a -> a`
*
* The least element of a non-empty structure.
*/
export const minimum = foldr (min) (Infinity)
// Specialized folds
/**
* `concat :: Foldable t => t [a] -> [a]`
*
* The concatenation of all the elements of a container of lists.
*/
export const concat =
<A> (x: Foldable<List<A>>): List<A> =>
foldr<List<A>, List<A>> (append) (Nil) (x)
/**
* `concatMap :: Foldable t => (a -> [b]) -> t a -> [b]`
*
* Map a function over all the elements of a container and concatenate the
* resulting lists.
*/
export const concatMap =
<A, B> (f: (x: A) => List<B>) =>
foldr<A, List<B>> (pipe (f, append)) (Nil)
/**
* `and :: Foldable t => t Bool -> Bool`
*
* `and` returns the conjunction of a container of Bools. For the result to be
* `True`, the container must be finite. `False`, however, results from a
* `False` value finitely far from the left end.
*
* ```haskell
* and Nothing = true
* and Just x = x
* ```
*/
export const and =
(x: Foldable<boolean>): boolean => {
if (isList (x)) {
return isNil (x) ? true : x .x && and (x .xs)
}
if (isOrderedMap (x)) {
return [...x .value .values ()] .every (ident)
}
if (isOrderedSet (x)) {
return [...x .value] .every (ident)
}
if (isEither (x)) {
if (isLeft (x)) {
return true
}
return x .value
}
if (isMaybe (x)) {
if (isNothing (x)) {
return true
}
return x .value
}
throw new TypeError (instanceErrorMsg ("and") (x))
}
/**
* `or :: Foldable t => t Bool -> Bool`
*
* `or` returns the disjunction of a container of Bools. For the result to be
* `False`, the container must be finite. `True`, however, results from a
* `True` value finitely far from the left end.
*
* ```haskell
* or Nothing = false
* or Just x = x
* ```
*/
export const or =
(x: Foldable<boolean>): boolean => {
if (isList (x)) {
return isNil (x) ? false : x .x || or (x .xs)
}
if (isOrderedMap (x)) {
return [...x .value .values ()] .some (ident)
}
if (isOrderedSet (x)) {
return [...x .value] .some (ident)
}
if (isEither (x)) {
if (isLeft (x)) {
return false
}
return x .value
}
if (isMaybe (x)) {
if (isNothing (x)) {
return false
}
return x .value
}
throw new TypeError (instanceErrorMsg ("or") (x))
}
interface FoldableAnyPredSpecific<A, A1 extends A> {
(x: Either<any, A>): x is Right<A1>
(x: Maybe<A>): x is Just<A1>
(x: Foldable<A>): x is Foldable<A1>
}
interface FoldableAnySpecific<A> {
(x: Either<any, A>): x is Right<A>
(x: List<A>): x is Cons<A>
(x: Maybe<A>): x is Just<A>
(x: Foldable<A>): boolean
}
interface FoldableAny {
<A, A1 extends A> (f: (x: A) => x is A1): FoldableAnyPredSpecific<A, A1>
<A> (f: (x: A) => boolean): FoldableAnySpecific<A>
}
/**
* `any :: Foldable t => (a -> Bool) -> t a -> Bool`
*
* Determines whether any element of the structure satisfies the predicate.
*/
export const any: FoldableAny =
<A> (f: (x: A) => boolean) => (x: Foldable<A>): x is any => {
if (isList (x)) {
return isNil (x) ? false : f (x .x) || any (f) (x .xs)
}
if (isOrderedMap (x)) {
return [...x .value .values ()] .some (f)
}
if (isOrderedSet (x)) {
return [...x .value] .some (f)
}
if (isEither (x)) {
if (isLeft (x)) {
return false
}
return f (x .value)
}
if (isMaybe (x)) {
if (isNothing (x)) {
return false
}
return f (x .value)
}
throw new TypeError (instanceErrorMsg ("any") (x))
}
/**
* `all :: Foldable t => (a -> Bool) -> t a -> Bool`
*
* Determines whether all elements of the structure satisfy the predicate.
*/
export const all =
<A> (f: (x: A) => boolean) => (x: Foldable<A>): boolean => {
if (isList (x)) {
return isNil (x) ? true : f (x .x) && all (f) (x .xs)
}
if (isOrderedMap (x)) {
return [...x .value .values ()] .every (f)
}
if (isOrderedSet (x)) {
return [...x .value] .every (f)
}
if (isEither (x)) {
if (isLeft (x)) {
return true
}
return f (x .value)
}
if (isMaybe (x)) {
if (isNothing (x)) {
return true
}
return f (x .value)
}
throw new TypeError (instanceErrorMsg ("all") (x))
}
// Searches
/**
* `notElem :: (Foldable t, Eq a) => a -> t a -> Bool`
*
* `notElem` is the negation of `elem`.
*/
export const notElem = <A> (e: A) => pipe (elem (e), not)
/**
* `notElemF :: (Foldable t, Eq a) => a -> t a -> Bool`
*
* `notElemF` is the negation of `elem_`.
*
* `notElemF` is the same as `notElem` but with arguments flipped.
*/
export const notElemF = <A> (x: Foldable<A>) => (e: A) => notElem (e) (x)
interface Find {
/**
* `find :: Foldable t => (a -> Bool) -> t a -> Maybe a`
*
* The `find` function takes a predicate and a structure and returns the
* leftmost element of the structure matching the predicate, or `Nothing` if
* there is no such element.
*/
<A, A1 extends A> (pred: (x: A) => x is A1): (x: Foldable<A>) => Maybe<A1>
/**
* `find :: Foldable t => (a -> Bool) -> t a -> Maybe a`
*
* The `find` function takes a predicate and a structure and returns the
* leftmost element of the structure matching the predicate, or `Nothing` if
* there is no such element.
*/
<A> (pred: (x: A) => boolean): (x: Foldable<A>) => Maybe<A>
}
/**
* `find :: Foldable t => (a -> Bool) -> t a -> Maybe a`
*
* The `find` function takes a predicate and a structure and returns the
* leftmost element of the structure matching the predicate, or `Nothing` if
* there is no such element.
*/
export const find: Find =
<A> (pred: (x: A) => boolean) => (x: Foldable<A>): Maybe<A> => {
if (isList (x)) {
return isNil (x) ? Nothing : pred (x .x) ? Just (x .x) : find (pred) (x .xs)
}
if (isOrderedMap (x)) {
return Maybe ([...x .value .values ()] .find (pred))
}
if (isOrderedSet (x)) {
return Maybe ([...x .value] .find (pred))
}
if (isEither (x)) {
return isRight (x) && pred (x .value) ? Just (x .value) : Nothing
}
if (isMaybe (x)) {
return isJust (x) && pred (x .value) ? x : Nothing
}
throw new TypeError (instanceErrorMsg ("all") (x))
}
const instanceErrorMsg =
(fname: string) =>
(x: any) =>
`${fname}: missing instance of Foldable\n${showP (x)}`
const emptyErrorMsg =
(fname: string) =>
(x: any) =>
`${fname}: empty structure\n${showP (x)}`
export const Foldable = {
foldr,
foldl,
foldr1,
foldl1,
toList,
fnull,
length,
elem,
elemF,
sum,
product,
maximum,
minimum,
concat,
concatMap,
and,
or,
any,
all,
notElem,
notElemF,
find,
}