refactor
This commit is contained in:
parent
c951b97dc9
commit
a18a759db4
8 changed files with 401 additions and 152 deletions
26
README.md
26
README.md
|
@ -4,33 +4,19 @@ a small functional/monad library
|
|||
# Usage
|
||||
## Example
|
||||
```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 thou = Identity(1)
|
||||
i.bind(x => thou.map(y => x + y)) // Identity(2)
|
||||
|
||||
const maybe = Some('thing')
|
||||
const maybe = Option.of('thing')
|
||||
maybe.isSome() // true
|
||||
const isnt = maybe.bind(() => None).map(_x => 'other') // None
|
||||
const isnt = maybe.chain(None).map(_x => 'other') // None
|
||||
isnt.isSome() // false
|
||||
|
||||
const either = Right('neutron')
|
||||
|
||||
const value = either.match({
|
||||
Right(value) {
|
||||
return value
|
||||
},
|
||||
Left(value) {
|
||||
console.error('oh no')
|
||||
}
|
||||
}) // 'neutron'
|
||||
|
||||
const result = Ok('3:41am')
|
||||
const result = Result.of('3:41am')
|
||||
result.isOk() // true
|
||||
|
||||
result.bind(() => new Error(-Infinity))
|
||||
result.bind(() => Err(new Error(-Infinity)))
|
||||
.map(_x => '4:10am')
|
||||
.bind(Ok)
|
||||
|
||||
result.isErr() // true
|
||||
```
|
||||
|
|
15
jsconfig.json
Normal file
15
jsconfig.json
Normal 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
51
src/algebra/common.js
Normal 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
3
src/algebra/index.js
Normal 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
128
src/algebra/option.js
Normal 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
122
src/algebra/result.js
Normal 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
73
src/curry.js
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
135
src/index.js
135
src/index.js
|
@ -1,134 +1,5 @@
|
|||
export class MatchError extends Error {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
import { Result } from './algebra/result.js'
|
||||
|
||||
const a = Result.of(2).chain(x => Result.zero())
|
||||
console.log(a)
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue