initial commit

This commit is contained in:
Rowan 2025-04-08 17:39:58 -05:00
commit d9daed0d09
14 changed files with 470 additions and 0 deletions

15
jsconfig.json Normal file
View file

@ -0,0 +1,15 @@
{
"compilerOptions": {
"module": "es2020",
"target": "es6",
"lib": ["esnext", "dom"],
"checkJs": true,
"paths": {
"/*": ["./*"]
}
},
"exclude": [
"node_modules"
]
}

13
package.json Normal file
View file

@ -0,0 +1,13 @@
{
"name": "izuna",
"version": "1.0.0",
"author": "Rowan <rowan@kitsu.cafe> (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": ""
}

83
src/curry.js Normal file
View file

@ -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<Tail<T>>; 1: Head<T> }[HasTail<T> 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<N, Tail<T>, Prepend<any, I>>; 1: T }[Length<I> extends N ? 1 : 0]} Drop
*/
/**
* @template X, Y
* @typedef {X extends Y ? X : Y} Cast
*/
/**
* @template {any[]} P, R
* @typedef {<T extends any[]>(...args: Cast<T, Partial<P>>) => Drop<Length<T>, P> extends [any, ...any[]] ? Curried<Cast<Drop<Length<T>, P>, any[]>, R> : R} Curried
*/
/**
* @template {any[]} P, R
* @param {number} arity
* @param {(...args: P) => R} func
* @returns {Curried<P, R>}
*/
export function curryN(arity, func) {
return function curried(...args) {
if (args.length >= arity) {
return func.apply(this, args)
} else {
/** @type {Curried<P, R>} */
return function(...args2) {
return curried.apply(this, args.concat(args2))
}
}
}
}
/**
* @template {any[]} P, R
* @param {(...args: P) => R} func
* @returns {Curried<P, R>}
*/
export function curry(func) {
return curryN(func.length, func)
}

26
src/fantasy-land.js Normal file
View file

@ -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)
}
}

138
src/function.js Normal file
View file

@ -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<U, V>} f
* @param {Morphism<T, U>} 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<T>} pred
* @param {InferredMorphism<T>} pass
* @param {InferredMorphism<T>} fail
* @param {T} x
*/
(pred, pass, fail, x) => (
pred(x) ? pass(x) : fail(x)))
export const when = curry(
/**
* @template T
* @param {Predicate<T>} pred
* @param {InferredMorphism<T>} pass
* @param {T} x
*/
(pred, pass, x) => ifElse(pred, pass, id, x))
export const unless = curry(
/**
* @template T
* @param {Predicate<T>} pred
* @param {InferredMorphism<T>} fail
* @param {T} x
*/
(pred, fail, x) => ifElse(pred, id, fail, x))
/**
* @template A, B
* @template {Morphism<A, B>} [F=Morphism<A, B>]
* @typedef {(f: F, a: A) => B} StaticMorphism
*/
export const ap = curry(
/**
* @template A, B
* @template {Morphism<A, B>} Ap
* @param {Morphism<A, B>} 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) => {
})

10
src/index.js Normal file
View file

@ -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'

123
src/list.js Normal file
View file

@ -0,0 +1,123 @@
import { curry } from './curry.js'
import { inc } from './math.js'
/**
* @template T
* @param {T | Iterable<T> | Iterator<T>} value
* @returns {value is Iterable<T>}
*/
export function isIterable(value) {
return value[Symbol.iterator] != null
}
/**
* @template T
* @param {T | Iterable<T> | Iterator<T>} value
* @yields {T}
*/
export function* iter(value) {
if (isIterable(value)) {
yield* Iterator.from(value)
} else {
yield value
}
}
/**
* @template T
* @param {...(Iterable<T> | Iterator<T>)} 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<T>} 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<T>} 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<T>} 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<T>} a
* @returns {T}
*/
export const head = a => nth(0, a)
/**
* @template T
* @param {T[] | Iterable<T>} 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<T>} xs
*/
(x, xs) => concat([x], xs))

0
src/logic.js Normal file
View file

12
src/math.js Normal file
View file

@ -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

0
src/object.js Normal file
View file

0
src/relation.js Normal file
View file

0
src/string.js Normal file
View file

27
src/type.js Normal file
View file

@ -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

23
src/types.js Normal file
View file

@ -0,0 +1,23 @@
export default {}
/**
* @typedef {(...v: any) => any} Fn
*/
/**
* @template T, U
* @typedef {(value:T) => U} Morphism
*/
/**
* @template T
* @typedef {<U>(value:T) => U} InferredMorphism
*/
/**
* @template T
* @typedef {(value: T) => boolean} Predicate
*/