import { Lazy } from 'fp-ts/function'
export const run = (f: Lazy): A => f()
export function curry(f: (a: A, b: B) => C): (a: A) => (b: B) => C
export function curry(
f: (a: A, b: B, c: C) => D,
): (a: A) => (b: B) => (c: C) => D
export function curry(f: (...args: any) => any) {
return function curried(...head: ReadonlyArray) {
return head.length >= f.length
? f(...head)
: (...rest: ReadonlyArray) => curried(...head.concat(rest))
}
}
export function uncurry(
f: (a: A) => (b: B) => (c: C) => D,
): D extends (...args: any) => any ? never : (a: A, b: B, c: C) => D
export function uncurry(
f: (a: A) => (b: B) => C,
): C extends (...args: any) => any ? never : (a: A, b: B) => C
export function uncurry(f: (...args: any) => any) {
return (...args: ReadonlyArray) =>
args.reduce((_f: any, arg) => _f(arg), f)
}
export const memoize = any>(f: A): A => {
const cache: Record> = {}
return ((...args: any): ReturnType => {
let key = ''
try {
key = JSON.stringify(args)
} catch (error) {
return f(...args)
}
if (!(key in cache)) {
const result = f(...args)
cache[key] =
result instanceof Promise
? result.catch((error) => {
delete cache[key]
throw error
})
: result
}
return cache[key]
}) as any
}
/**
* @example
* const pi = (max: number) => (): ((n?: number) => number) =>
* recurse((self, depth) => (n = 1) =>
* 4 / n + (depth < max ? self(-1 * n + (n < 0 ? 2 : -2)) : 0)
* )
* const pi100 = pi(100)()()
*
* expect(pi100).toBeGreaterThan(3.15)
* expect(pi100).toBeLessThan(3.16)
*/
export const recurse = any>(
f: (self: A, depth: number) => A,
cache = false,
): A => {
let depth = 0
let _cache: A | null = null
const run = !cache
? f
: (_self: A, _depth: number): A => {
if (!_cache) {
_cache = f(_self, _depth)
}
return _cache
}
const self = ((...args: any) => run(self, depth++)(...args)) as A
return self
}
/**
* @example
* type _Op = { _tag: A; x: number; y: number }
* type Add = _Op<'Add'>
* type Mul = _Op<'Mul'>
* type Sub = _Op<'Sub'>
* type Div = _Op<'Div'>
* type Op = Add | Mul | Sub | Div
*
* const calc = match()({
* Add: ({ x, y }) => option.some(x + y),
* Mul: ({ x, y }) => option.some(x * y),
* Sub: ({ x, y }) => option.some(x - y),
* Div: ({ x, y }) => (0 === y ? option.none : option.some(x / y)),
* })
* const op = (_tag: Op['_tag']) => (y: number) => (x: number) => calc({ _tag, x, y })
* const add = op('Add')
* const mul = op('Mul')
* const sub = op('Sub')
* const div = op('Div')
*
* expect(
* pipe(
* option.of(42),
* option.chain(add(1138)),
* option.chain(mul(0.1)),
* option.chain(sub(1337)),
* option.chain(div(0.1)),
* option.getOrElse(() => NaN)
* )
* ).toBeCloseTo(-12190)
*/
export const match =
() =>
(
onCases: {
readonly [t in T]: (a: Extract) => B
},
k: K = '_tag' as K,
) =>
(a: A): B =>
onCases[a[k] as T](a as Extract)