This commit is contained in:
Rowan 2025-03-31 05:10:46 -05:00
parent c951b97dc9
commit a18a759db4
8 changed files with 401 additions and 152 deletions

View file

@ -4,33 +4,19 @@ a small functional/monad library
# Usage # Usage
## Example ## Example
```js ```js
import { Identity, Some, None, Left, Right, Ok, Err } from 'kojima' import { Option, Some, None, Result, Ok, Err } from 'kojima'
const i = Identity(1) const maybe = Option.of('thing')
const thou = Identity(1)
i.bind(x => thou.map(y => x + y)) // Identity(2)
const maybe = Some('thing')
maybe.isSome() // true maybe.isSome() // true
const isnt = maybe.bind(() => None).map(_x => 'other') // None const isnt = maybe.chain(None).map(_x => 'other') // None
isnt.isSome() // false isnt.isSome() // false
const either = Right('neutron') const result = Result.of('3:41am')
const value = either.match({
Right(value) {
return value
},
Left(value) {
console.error('oh no')
}
}) // 'neutron'
const result = Ok('3:41am')
result.isOk() // true result.isOk() // true
result.bind(() => new Error(-Infinity)) result.bind(() => Err(new Error(-Infinity)))
.map(_x => '4:10am') .map(_x => '4:10am')
.bind(Ok)
result.isErr() // true result.isErr() // true
``` ```

15
jsconfig.json Normal file
View file

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

51
src/algebra/common.js Normal file
View file

@ -0,0 +1,51 @@
export const Self = Symbol()
export const Value = Symbol()
export function constant() { return this }
/**
* @template T, U
* @typedef {(x: T) => U} Morphism
*/
/**
* @template T
* @typedef {{ [Value]: T }} Set
*/
/**
* @template T
* @typedef {{ [Self]: (value: T) => Applicative<T> }} Applicative
*/
/**
* @template T
* @param {T} value
* @returns {Set<T>}
*/
export const Set = value => ({ [Value]: value })
/**
* @template T, U
* @param {Morphism<T, U>} fn
* @returns {U}
*/
export function chain(fn) {
return fn(this[Value])
}
export function extract() {
return this[Value]
}
/**
* @template T
* @this {Applicative<T>}
* @param {T} value
* @returns {Applicative<T>}
*/
export function of(value) {
return this[Self](value)
}

3
src/algebra/index.js Normal file
View file

@ -0,0 +1,3 @@
export { Some, None, Option } from './option.js'
export { Ok, Err, Result } from './result.js'

128
src/algebra/option.js Normal file
View file

@ -0,0 +1,128 @@
import { chain, constant, Self, Value } from './common.js'
/** @import { Applicative, Morphism, Set } from './common.js' */
/**
* @template T
* @typedef {Some<T> | None} Option
*/
/**
* @template T
* @typedef SomeMethods
* @property {typeof isSome} isSome
* @property {typeof isNone} isNone
* @property {typeof chain} chain
* @property {typeof map} map
* @property {typeof alt} alt
* @property {typeof fold} fold
*/
/**
* @template T
* @typedef {Applicative<T> & Set<T> & SomeMethods<T>} Some
* @variation 1
*/
/**
* @typedef None
* @property {typeof isSome} isSome
* @property {typeof isNone} isNone
* @property {typeof chain} chain
* @property {typeof map} map
* @property {typeof alt} alt
* @property {typeof fold} fold
*/
/**
* @template T, U
* @this {Option<T>}
* @param {Morphism<T, U>} fn
* @returns {Option<U>}
*/
function map(fn) {
return this[Self](this.chain(fn))
}
/**
* @template T, U
* @this {Option<T>}
* @param {Morphism<T, U>} fn
* @param {U} acc
* @return {U}
*/
function fold(fn, acc) {
const result = this.map(fn)
return result.isSome() ? result[Value] : acc
}
/**
* @template T
* @this {Option<T>}
* @param {Option<T>} other
* @returns {Option<T>}
*/
function alt(other) {
return this.isSome() ? this : other
}
/**
* @template T
* @this {Option<T>}
* @returns {this is Some<T>}
*/
function isSome() {
return this[Self] === Some
}
/**
* @template T
* @this {Option<T>}
* @returns {this is None}
*/
function isNone() {
return this === none
}
/**
* @template T
* @param {?T} value
* @variation 2
* @returns {Some<T>}
*/
export const Some = value => ({
[Self]: Some,
[Value]: value,
isSome,
isNone,
chain,
map,
alt,
fold
})
/**
* @returns {None}
*/
export const None = () => none
/** @type {None} */
export const none = ({
isSome,
isNone,
chain: constant,
map: constant,
alt,
fold
})
/**
* @template T
* @param {?T} value
* @returns {Option<T>}
*/
export const Option = value => Some(value)
Option.of = Option
Option.zero = None

122
src/algebra/result.js Normal file
View file

@ -0,0 +1,122 @@
import { chain, constant, Self, Value } from './common.js'
/** @import { Morphism } from './common.js' */
/**
* @template T, E
* @typedef {Ok<T> | Err<E>} Result
*/
/**
* @template T
* @typedef Ok
* @property {typeof isOk} isOk
* @property {typeof isErr} isErr
* @property {typeof chain} chain
* @property {typeof map} map
* @property {typeof alt} alt
* @property {typeof fold} fold
*/
/**
* @template T
* @typedef Err
* @property {typeof isOk} isOk
* @property {typeof isErr} isErr
* @property {typeof chain} chain
* @property {typeof map} map
* @property {typeof alt} alt
* @property {typeof fold} fold
*/
/**
* @template T, U, E
* @this {Result<T, E>}
* @param {Morphism<T, U>} fn
* @returns {Result<U, E>}
*/
function map(fn) {
return this[Self](this.chain(fn))
}
/**
* @template T, U, E
* @this {Result<T, E>}
* @param {Morphism<T, Result<U, E>>} fn
* @param {U} acc
* @return {U}
*/
function fold(fn, acc) {
const result = this.map(fn)
return result.isOk() ? result[Value] : acc
}
/**
* @template T, E
* @this {Result<T, E>}
* @param {Result<T, E>} other
* @returns {Result<T, E>}
*/
function alt(other) {
return this.isOk() ? this : other
}
/**
* @template T, E
* @this {Result<T, E>}
* @returns {this is Ok<T>}
*/
function isOk() {
return this[Self] === Ok
}
/**
* @template T, E
* @this {Result<T, E>}
* @returns {this is Err<E>}
*/
function isErr() {
return this[Self] === Err
}
/**
* @template T
* @param {?T} value
* @returns {Ok<T>}
*/
export const Ok = value => Object.freeze({
[Self]: Ok,
[Value]: value,
isOk,
isErr,
chain,
map,
alt,
fold
})
/**
* @template T
* @param {T?} value
* @returns {Err<T>}
*/
export const Err = value => Object.freeze({
[Self]: Err,
[Value]: value,
chain: constant,
map: constant,
alt,
isOk,
isErr,
fold
})
/**
* @template T, E
* @param {T} value
* @returns {Result<T, E>}
*/
export const Result = value => Ok(value)
Result.of = Result
Result.zero = () => Err(undefined)

73
src/curry.js Normal file
View file

@ -0,0 +1,73 @@
/**
* @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, T>>; 1: T }[Length<I> extends N ? 1 : 0]} Drop
* @typedef {Drop<2, [0, 1, 2, 3, 4]>} t00
*/
/**
* @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[]] ? Curry<Cast<Drop<Length<T>, P>, any[]>, R> : R} Curry
*/
/**
* @template {any[]} P, R
* @param {(...args: P) => R} func
* @returns {Curry<P, R>}
*/
export function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args)
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2))
}
}
}
}

View file

@ -1,134 +1,5 @@
export class MatchError extends Error { import { Result } from './algebra/result.js'
constructor(name) {
super(`Pattern not matched: ${name}`)
}
}
const ValueKey = Symbol()
const create = (...args) => Object.freeze(Object.assign(...args))
function constant() { return this }
function extract() { return this[ValueKey] }
function extend(f) { return this.of(f(this)) }
function bind(fn) { return fn(this[ValueKey]) }
function ap(other) { return other.map(this[ValueKey]) }
function map(fn) {
return this.of(this.bind(fn))
}
function match(pattern) {
const name = this.of.name
if (name in pattern) {
return this.bind(pattern[name])
} else if ('_' in pattern) {
return pattern['_']()
} else {
throw new MatchError(name)
}
}
const Value = v => ({ [ValueKey]: v })
const Functor = {
map
}
const ConstantFunctor = {
map: constant
}
const ConstantMonad = of => ({
of,
bind: constant
})
const PointedFunctor = of => ({
of
})
const Applicative = {
...Functor,
ap
}
const Monad = of => ({
...PointedFunctor(of),
bind
})
const Comonad = {
extract,
extend
}
export const Identity = value => create(
Value(value),
Functor,
Monad(Identity)
)
export const Constant = value => create(
Value(value),
ConstantFunctor,
ConstantMonad(Constant),
)
export const Ok = value => create(
Value(value),
Functor,
Monad(Ok),
{
isOk: true,
isErr: false,
match
})
export const Err = value => create(
Value(value),
ConstantFunctor,
ConstantMonad(Err),
{
isOk: false,
isErr: true,
match
}
)
export const Some = value => create(
Value(value),
Functor,
Monad(Some),
{
isSome: true,
isNone: false,
match
}
)
export const None = () => NoneConstant
const NoneConstant = create(
ConstantFunctor,
ConstantMonad(None),
{
isSome: false,
isNone: true,
match
}
)
export function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args)
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2))
}
}
}
}
const a = Result.of(2).chain(x => Result.zero())
console.log(a)