From d9daed0d0977f7b79462fb204a5d89b827dcac1b Mon Sep 17 00:00:00 2001 From: rowan Date: Tue, 8 Apr 2025 17:39:58 -0500 Subject: [PATCH] initial commit --- jsconfig.json | 15 +++++ package.json | 13 +++++ src/curry.js | 83 ++++++++++++++++++++++++++ src/fantasy-land.js | 26 +++++++++ src/function.js | 138 ++++++++++++++++++++++++++++++++++++++++++++ src/index.js | 10 ++++ src/list.js | 123 +++++++++++++++++++++++++++++++++++++++ src/logic.js | 0 src/math.js | 12 ++++ src/object.js | 0 src/relation.js | 0 src/string.js | 0 src/type.js | 27 +++++++++ src/types.js | 23 ++++++++ 14 files changed, 470 insertions(+) create mode 100644 jsconfig.json create mode 100644 package.json create mode 100644 src/curry.js create mode 100644 src/fantasy-land.js create mode 100644 src/function.js create mode 100644 src/index.js create mode 100644 src/list.js create mode 100644 src/logic.js create mode 100644 src/math.js create mode 100644 src/object.js create mode 100644 src/relation.js create mode 100644 src/string.js create mode 100644 src/type.js create mode 100644 src/types.js diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..4f05d0e --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "module": "es2020", + "target": "es6", + "lib": ["esnext", "dom"], + "checkJs": true, + "paths": { + "/*": ["./*"] + } + }, + "exclude": [ + "node_modules" + ] +} + diff --git a/package.json b/package.json new file mode 100644 index 0000000..27ea3af --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "izuna", + "version": "1.0.0", + "author": "Rowan (https://kitsu.cafe)", + "type": "module", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "license": "GPL-3.0-or-later", + "description": "" +} diff --git a/src/curry.js b/src/curry.js new file mode 100644 index 0000000..5dbbc52 --- /dev/null +++ b/src/curry.js @@ -0,0 +1,83 @@ +/** + * @template {(...args: any[]) => any} F + * @typedef {F extends ((...args: infer A) => any) ? A : never} Params + */ + +/** + * @template {any[]} T + * @typedef {T extends [any, ...any[]] ? T[0] : never} Head + */ + +/** + * @template {any[]} T + * @typedef {((...t: T) => any) extends ((_: any, ...tail: infer TT) => any) ? TT : []} Tail + */ + +/** + * @template {any[]} T + * @typedef {T extends ([] | [any]) ? false : true} HasTail + */ + +/** + * @template {any[]} T + * @typedef {{ 0: Last>; 1: Head }[HasTail extends true ? 0 : 1]} Last + */ + +/** + * @template {any[]} T + * @typedef {T['length']} Length + */ + +/** + * @template E + * @template {any[]} T + * @typedef {((head: E, ...args: T) => any) extends ((...args: infer U) => any) ? U : T} Prepend + */ + +/** + * @template {number} N + * @template {any[]} T + * @template {any[]} [I = []] + * @typedef {{ 0: Drop, Prepend>; 1: T }[Length extends N ? 1 : 0]} Drop + */ + +/** + * @template X, Y + * @typedef {X extends Y ? X : Y} Cast + */ + +/** + * @template {any[]} P, R + * @typedef {(...args: Cast>) => Drop, P> extends [any, ...any[]] ? Curried, P>, any[]>, R> : R} Curried + */ + +/** + * @template {any[]} P, R + * @param {number} arity + * @param {(...args: P) => R} func + * @returns {Curried} + */ +export function curryN(arity, func) { + return function curried(...args) { + if (args.length >= arity) { + return func.apply(this, args) + } else { + /** @type {Curried} */ + return function(...args2) { + return curried.apply(this, args.concat(args2)) + } + } + } +} + + +/** + * @template {any[]} P, R + * @param {(...args: P) => R} func + * @returns {Curried} + */ +export function curry(func) { + return curryN(func.length, func) +} + + diff --git a/src/fantasy-land.js b/src/fantasy-land.js new file mode 100644 index 0000000..aa00a55 --- /dev/null +++ b/src/fantasy-land.js @@ -0,0 +1,26 @@ +/** + * @param {PropertyKey[]} methods + * @param {Fn} f + */ +export function dispatch(methods, f) { + return function() { + if (arguments.length === 0) { + return f() + } + + const args = Array.from(arguments) + const obj = last(args) + + if (!isArray(obj)) { + for (let i = 0; i < obj.length; i++) { + const fn = obj[methods[i]] + if (isFn(fn)) { + return fn.apply(obj, args.slice(0, -1)) + } + } + } + + return f.apply(this, arguments) + } +} + diff --git a/src/function.js b/src/function.js new file mode 100644 index 0000000..0be2312 --- /dev/null +++ b/src/function.js @@ -0,0 +1,138 @@ +import { curry, curryN } from './curry.js' +import { concat, iter } from './list.js' + +/** @import { Fn, Morphism, InferredMorphism, Predicate } from './types.js' */ + +/** + * @template T + * @param {T} x + * @returns {x} + */ +export const id = x => x + +/** + * @param {...Fn} fns + * @returns {(x: any) => any} + */ +export const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x) + +export const compose = curry( + /** + * @template T, U, V + * @param {Morphism} f + * @param {Morphism} g + * @param {T} x + * @returns V + */ + (f, g, x) => chain(g, chain(f, x)) +) + +/** + * @template T + * @param {T | T[]} a + * @Returns {T[]} + */ +export const liftA = a => Array.isArray(a) ? a : [a] + +/** + * @template T, U, R + * @param {(x: T, y: U) => R} binary + * @param {T} x + * @param {U} y + * @returns {R} + */ +export const liftF = curry((binary, x, y) => binary(x, y)) + +/** + * @param {number} a + * @param {number} b + * @returns {number} + */ +export const add = (a, b) => a + b + +/** + * @template T + * @param {T} x + * @returns {() => T} + */ +export const thunk = x => () => x + +/** + * @template T, U + * @template {(a: T, b: U, ...rest: any[]) => any} F + * @param {F} fn + * @returns {(b: U, a: T, ...rest: any[]) => any} + */ +export function flip(fn) { + return curryN(fn.length, function(a, b) { + const args = Array.prototype.slice.call(arguments, 0) + args[0] = b + args[1] = a + return fn.apply(this, args) + }) +} + +export const ifElse = curry( + /** + * @template T + * @param {Predicate} pred + * @param {InferredMorphism} pass + * @param {InferredMorphism} fail + * @param {T} x + */ + (pred, pass, fail, x) => ( + pred(x) ? pass(x) : fail(x))) + +export const when = curry( + /** + * @template T + * @param {Predicate} pred + * @param {InferredMorphism} pass + * @param {T} x + */ + (pred, pass, x) => ifElse(pred, pass, id, x)) + +export const unless = curry( + /** + * @template T + * @param {Predicate} pred + * @param {InferredMorphism} fail + * @param {T} x + */ + (pred, fail, x) => ifElse(pred, id, fail, x)) + +/** + * @template A, B + * @template {Morphism} [F=Morphism] + * @typedef {(f: F, a: A) => B} StaticMorphism + */ + +export const ap = curry( + /** + * @template A, B + * @template {Morphism} Ap + * @param {Morphism} f + * @param {{ ap: Ap } | Ap} a + */ + (f, a) => { + const fs = liftA(dispatchF('ap', f)) + const args = liftA(a) + + const xs = fs.reduce((acc, f) => ( + concat(acc, iter(map(f, args))) + ), []) + + return [...xs] + }) + +export const chain = curry( + function chain(f, a) { + }) + +export const map = curry((f, a) => { +}) + +export const reduce = curry((f, acc, xs) => { + +}) + diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..9942674 --- /dev/null +++ b/src/index.js @@ -0,0 +1,10 @@ +export * from './curry.js' +export * from './function.js' +export * from './list.js' +export * from './logic.js' +export * from './math.js' +export * from './object.js' +export * from './relation.js' +export * from './string.js' +export * from './type.js' + diff --git a/src/list.js b/src/list.js new file mode 100644 index 0000000..7f1d246 --- /dev/null +++ b/src/list.js @@ -0,0 +1,123 @@ +import { curry } from './curry.js' +import { inc } from './math.js' + +/** + * @template T + * @param {T | Iterable | Iterator} value + * @returns {value is Iterable} + */ +export function isIterable(value) { + return value[Symbol.iterator] != null +} + +/** + * @template T + * @param {T | Iterable | Iterator} value + * @yields {T} + */ +export function* iter(value) { + if (isIterable(value)) { + yield* Iterator.from(value) + } else { + yield value + } +} + +/** + * @template T + * @param {...(Iterable | Iterator)} iterators + * @yields {T} + */ +export const concat = function*(...iterators) { + for (const iter of iterators) { + for (const item of Iterator.from(iter)) { + yield item + } + } +} +/** + * @template T + * @param {number} i + * @param {T[] | Iterable} a + * @returns {T} + */ +export const nth = (i, a) => { + if (Array.isArray(a)) { + return a[i] + } else { + return /** @type {T} */ (iter(a).find((_, n) => i === n)) + } +} + + +/** + * @template T + * @param {number} n + * @param {T[] | Iterable} a + * @returns {a} + */ +export const take = (n, a) => { + if ('slice' in a) { + return a.slice(0, n) + } else { + iter(a).take(n) + return a + } +} + +/** + * @template T + * @param {number} n + * @param {T[] | Iterable} a + * @returns {a} + */ +export const drop = (n, a) => { + if ('slice' in a) { + return a.slice(n) + } else { + iter(a).drop(n) + return a + } +} + +/** + * @param {ArrayLike} a + * @returns {number} + */ +export const length = a => { + if ('length' in a) { + return a.length + } else { + return iter(a).reduce(inc, 0) + } +} + +/** + * @template T + * @param {T[] | Iterable} a + * @returns {T} + */ +export const head = a => nth(0, a) + +/** + * @template T + * @param {T[] | Iterable} a + * @returns {a} + */ +export const tail = a => drop(1, a) + +/** + * @template T + * @param {T[]} a + */ +export const last = a => nth(length(a) - 1, a) + +export const prepend = curry( + /** + * @template T + * @param {T} x + * @param {Iterable} xs + */ + (x, xs) => concat([x], xs)) + + diff --git a/src/logic.js b/src/logic.js new file mode 100644 index 0000000..e69de29 diff --git a/src/math.js b/src/math.js new file mode 100644 index 0000000..84847be --- /dev/null +++ b/src/math.js @@ -0,0 +1,12 @@ +/** + * @param {number} a + * @returns {number} + */ +export const inc = a => a + 1 + +/** + * @param {number} a + * @returns {number} + */ +export const dec = a => a - 1 + diff --git a/src/object.js b/src/object.js new file mode 100644 index 0000000..e69de29 diff --git a/src/relation.js b/src/relation.js new file mode 100644 index 0000000..e69de29 diff --git a/src/string.js b/src/string.js new file mode 100644 index 0000000..e69de29 diff --git a/src/type.js b/src/type.js new file mode 100644 index 0000000..3c5e4a0 --- /dev/null +++ b/src/type.js @@ -0,0 +1,27 @@ +/** + * @typedef {"string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"} TypeOfValue + */ + +/** + * @template T + * @param {T} x + * @returns {TypeOfValue} + */ +export const type = x => typeof x + +export const is = curry( + /** + * @template T + * @param {FunctionConstructor} ctr + * @param {T} x + */ + (ctr, x) => x.constructor === ctr) + +/** + * @template T + * @param {T} x + */ +export const isFn = x => type(x) === 'function' + +export const isArray = Array.isArray + diff --git a/src/types.js b/src/types.js new file mode 100644 index 0000000..fd54b39 --- /dev/null +++ b/src/types.js @@ -0,0 +1,23 @@ +export default {} + +/** + * @typedef {(...v: any) => any} Fn + */ + +/** + * @template T, U + * @typedef {(value:T) => U} Morphism + */ + +/** + * @template T + * @typedef {(value:T) => U} InferredMorphism + */ + +/** + * @template T + * @typedef {(value: T) => boolean} Predicate + */ + + +