v2
This commit is contained in:
parent
b997bca5e3
commit
1ccb2b1b1c
34 changed files with 1838 additions and 4311 deletions
259
README.md
259
README.md
|
@ -1,259 +0,0 @@
|
|||
# kojima
|
||||
a small functional/monad library
|
||||
|
||||
# Usage
|
||||
## Example
|
||||
```js
|
||||
import { Option, Some, None, Result, Ok, Err } from 'kojima'
|
||||
|
||||
const maybe = Option.of('thing')
|
||||
maybe.isSome() // true
|
||||
const isnt = maybe.chain(None).map(_x => 'other') // None
|
||||
isnt.isSome() // false
|
||||
|
||||
const result = Result.of('3:41am')
|
||||
result.isOk() // true
|
||||
|
||||
result.chain(() => Err(new Error(-Infinity)))
|
||||
.map(_x => '4:10am')
|
||||
.chain(Ok)
|
||||
|
||||
result.isErr() // true
|
||||
|
||||
// crimes!
|
||||
None == None() // true
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
### curry
|
||||
> (* -> a) -> (* -> a)
|
||||
|
||||
Returns a curried equivalent of the provided function.
|
||||
|
||||
```js
|
||||
import { curry } from './curry.js'
|
||||
|
||||
const add = (a, b, c) => a + b + c
|
||||
const curriedAdd = curry(add)
|
||||
const add1 = curriedAdd(1)
|
||||
const add3 = add1(2)
|
||||
const add4 = curriedAdd(2, 2)
|
||||
|
||||
const six = add3(3) // 6
|
||||
const eight = add4(2) // 7
|
||||
const twelve = curriedAdd(4, 4, 4) // 12
|
||||
```
|
||||
|
||||
### Option
|
||||
> Option\<T\> = Some\<T\> | None
|
||||
|
||||
Represents a value which may not exist.
|
||||
|
||||
|
||||
#### Methods
|
||||
##### of :: Option f => a -> f a
|
||||
> Option.of\<a\>(value: a) -> Option\<a\>
|
||||
|
||||
Creates a new `Some<T>` from `T`
|
||||
|
||||
```js
|
||||
const some = Option.of(1) // Some<number>(1)
|
||||
```
|
||||
|
||||
##### zero :: Option f => () -> f a
|
||||
> Option.zero() -> Option\<()\>
|
||||
|
||||
Creates a new `None`
|
||||
|
||||
```js
|
||||
const none = Option.zero() // None
|
||||
```
|
||||
|
||||
##### chain :: Option m => m a ~> (a -> m b) -> m b
|
||||
> Option\<T\>.chain\<U\>(fn: (value: T) -> Option\<U\>) -> Option\<U\>
|
||||
|
||||
Transform a `Option<T>` into `Option<U>` by applying `fn(T) -> Option<U>`.
|
||||
|
||||
```js
|
||||
const some = Option.of(1)
|
||||
const next = some.chain(x => Some(`value ${x}`)) // Some<string>('value 1')
|
||||
None.chain(x => Some(`value ${x}`)) // None
|
||||
const none = some.chain(None) // None
|
||||
none.chain(() => Some(1)) // None
|
||||
```
|
||||
|
||||
##### map :: Option f => f a ~> (a -> b) -> f b
|
||||
> Option\<T\>.map\<U\>(fn: (value: T) -> U) -> Option\<U\>
|
||||
|
||||
##### alt :: Option f => f a ~> f a -> f a
|
||||
> Option\<T\>.alt(other: Option\<T\>) -> Option\<T\>
|
||||
|
||||
Choose between either the first or second `Option` based on existence.
|
||||
|
||||
```js
|
||||
const some = Option.of(1)
|
||||
const none = Option.zero()
|
||||
|
||||
some.alt(none) // Some<number>(1)
|
||||
none.alt(some) // Some<number>(1)
|
||||
some.alt(Some(2)) // Some<number>(1)
|
||||
Some(2).alt(some) // Some<number>(2)
|
||||
none.alt(None) // None
|
||||
None.alt(none) // None
|
||||
```
|
||||
|
||||
##### fold :: Option f => f a ~> ((b, a) -> b, b) -> b
|
||||
> Option\<T\>.fold\<U\>(fn: ((acc: U, value: T) -> U, initial: U) -> U) -> U
|
||||
|
||||
Fold over a `Option`, accumulating the value. This is `unwrap_or_default` in Rust.
|
||||
|
||||
```js
|
||||
const some = Some(1)
|
||||
some.fold((acc, x) => x), 2) // 1
|
||||
some.fold((acc, x) => acc), 2) // 2
|
||||
|
||||
const none = Option.zero()
|
||||
none.fold((acc, x) => x, 2) // 2
|
||||
```
|
||||
|
||||
##### isSome :: Option f => () -> boolean
|
||||
> Option\<T\>.isSome() -> boolean
|
||||
|
||||
Returns a boolean based on whether the Option is `Some<T>`
|
||||
|
||||
```js
|
||||
Some(1).isSome() // true
|
||||
None.isSome() // false
|
||||
```
|
||||
|
||||
##### isNone :: Option f => () -> boolean
|
||||
> Option\<T\>.isNone() -> boolean
|
||||
|
||||
Returns a boolean based on whether the Option is `None`
|
||||
|
||||
```js
|
||||
Some(1).isNone() // false
|
||||
None.isNone() // true
|
||||
```
|
||||
|
||||
|
||||
### Result
|
||||
> Result<T, E> = Ok\<T\> | Err\<E\>
|
||||
|
||||
Represents the result of a computation which may fail.
|
||||
|
||||
#### Methods
|
||||
##### of :: Result f => a -> f a
|
||||
> Result.of\<a\>(value: a) -> Result\<a, ()\>
|
||||
|
||||
Creates a new `Ok<T>` from `T`
|
||||
|
||||
```js
|
||||
const ok = Result.of(1) // Ok<number>(1)
|
||||
```
|
||||
|
||||
##### zero :: Result f => () -> f a
|
||||
> Result.zero() -> Result\<(), ()\>
|
||||
|
||||
Creates a new `Err<()>`
|
||||
|
||||
```js
|
||||
const err = Result.zero() // Err<()>()
|
||||
```
|
||||
|
||||
##### chain :: Result m => m a ~> (a -> m b) -> m b
|
||||
> Result\<T, E\>.chain\<U\>(fn: (value: T) -> Result<U, E>) -> Result\<U, E\>
|
||||
|
||||
Transform a `Result<T, E>` into `Result<U, E>` by applying `fn(T) -> Result<U, E>`.
|
||||
|
||||
```js
|
||||
const ok = Result.of(1)
|
||||
const next = ok.chain(x => Ok(`value ${x}`)) // Ok<string>('value 1')
|
||||
Err(0).chain(x => Ok(1)) // Err(0)
|
||||
const err = next.chain(() => Err(0)) // Err(0)
|
||||
err.chain(() => Ok(1)) // Err(0)
|
||||
```
|
||||
|
||||
##### map :: Result f => f a ~> (a -> b) -> f b
|
||||
> Result\<T, E\>.map\<U\>(fn: (value: T) -> U) -> Result\<U, E\>
|
||||
|
||||
Transform a `Result<T, E>` into a `Result<U, E>` by applying `fn(T) -> U`
|
||||
|
||||
```js
|
||||
const ok = Result.of(1)
|
||||
const next = ok.map(x => `value ${x}`) // Ok<string>('value 1')
|
||||
Err(0).map(x => Ok(1)) // Err(0)
|
||||
|
||||
```
|
||||
|
||||
##### alt :: Result f => f a ~> f a -> f a
|
||||
> Result\<T, E\>.alt(other: Result\<T, E\>) -> Result\<T, E\>
|
||||
|
||||
Choose between either the first or second `Result` based on success.
|
||||
|
||||
```js
|
||||
const ok = Result.of(1)
|
||||
const err = Result.zero()
|
||||
|
||||
ok.alt(err) // Ok<number>(1)
|
||||
err.alt(ok) // Ok<number>(1)
|
||||
ok.alt(Ok(2)) // Ok<number>(1)
|
||||
Ok(2).alt(ok) // Ok<number>(2)
|
||||
err.alt(Err(new Error('wont see this'))) // Err<()>()
|
||||
Err(new Error('hi! :3')).alt(err) // Err<Error>(new Error('hi! :3'))
|
||||
```
|
||||
|
||||
##### fold :: Result f => f a ~> ((b, a) -> b, b) -> b
|
||||
> Result\<T, E\>.fold\<U\>(fn: ((acc: U, value: T) -> U, initial: U) -> U) -> U
|
||||
|
||||
Fold over a `Result`, accumulating the value. This is `unwrap_or_default` in Rust.
|
||||
|
||||
```js
|
||||
const ok = Ok(1)
|
||||
ok.fold((acc, x) => x), 2) // 1
|
||||
ok.fold((acc, x) => acc), 2) // 2
|
||||
|
||||
const err = Result.zero()
|
||||
err.fold((acc, x) => x, 2) // 2
|
||||
```
|
||||
|
||||
##### bimap :: Result f => f a c ~> (a -> b, c -> d) -> f b d
|
||||
> Result\<T1, E1\>.bimap\<T2, E2\>(x: (value: T1) -> T2, y: (error: E1) -> E2) -> Result\<T2, E2\>
|
||||
|
||||
```js
|
||||
const ok = Ok(1)
|
||||
|
||||
ok.bimap(
|
||||
x => x + 1,
|
||||
y => y * 2
|
||||
) // Ok(2)
|
||||
|
||||
const err = Err(4)
|
||||
|
||||
err.bimap(
|
||||
x => x + 1,
|
||||
y => y * 2
|
||||
) // Err(8)
|
||||
```
|
||||
|
||||
##### isOk :: Result f => () -> boolean
|
||||
> Result\<T, E\>.isOk() -> boolean
|
||||
|
||||
Returns a boolean based on whether the Result is `Ok<T>`
|
||||
|
||||
```js
|
||||
Ok(1).isOk() // true
|
||||
Err(1).isOk() // false
|
||||
```
|
||||
|
||||
##### isErr :: Result f => () -> boolean
|
||||
> Result\<T, E\>.isErr() -> boolean
|
||||
|
||||
Returns a boolean based on whether the Result is `Err<E>`
|
||||
|
||||
```js
|
||||
Ok(1).isErr() // false
|
||||
Err(1).isErr() // true
|
||||
```
|
||||
|
1372
dist/index.js
vendored
1372
dist/index.js
vendored
File diff suppressed because it is too large
Load diff
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "es2020",
|
||||
"target": "es6",
|
||||
"lib": ["esnext", "dom"],
|
||||
"checkJs": true,
|
||||
"paths": {
|
||||
"/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
|
485
package-lock.json
generated
485
package-lock.json
generated
|
@ -1,485 +0,0 @@
|
|||
{
|
||||
"name": "kojima",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "kojima",
|
||||
"version": "1.0.0",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.2",
|
||||
"izuna": "git+https://git.kitsu.cafe/rowan/izuna.git",
|
||||
"typescript": "^5.8.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"folktest": "git+https://git.kitsu.cafe/rowan/folktest.git"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz",
|
||||
"integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz",
|
||||
"integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz",
|
||||
"integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz",
|
||||
"integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz",
|
||||
"integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz",
|
||||
"integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz",
|
||||
"integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz",
|
||||
"integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz",
|
||||
"integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz",
|
||||
"integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz",
|
||||
"integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz",
|
||||
"integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz",
|
||||
"integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz",
|
||||
"integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz",
|
||||
"integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz",
|
||||
"integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz",
|
||||
"integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz",
|
||||
"integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz",
|
||||
"integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz",
|
||||
"integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz",
|
||||
"integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz",
|
||||
"integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz",
|
||||
"integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz",
|
||||
"integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz",
|
||||
"integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz",
|
||||
"integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.25.2",
|
||||
"@esbuild/android-arm": "0.25.2",
|
||||
"@esbuild/android-arm64": "0.25.2",
|
||||
"@esbuild/android-x64": "0.25.2",
|
||||
"@esbuild/darwin-arm64": "0.25.2",
|
||||
"@esbuild/darwin-x64": "0.25.2",
|
||||
"@esbuild/freebsd-arm64": "0.25.2",
|
||||
"@esbuild/freebsd-x64": "0.25.2",
|
||||
"@esbuild/linux-arm": "0.25.2",
|
||||
"@esbuild/linux-arm64": "0.25.2",
|
||||
"@esbuild/linux-ia32": "0.25.2",
|
||||
"@esbuild/linux-loong64": "0.25.2",
|
||||
"@esbuild/linux-mips64el": "0.25.2",
|
||||
"@esbuild/linux-ppc64": "0.25.2",
|
||||
"@esbuild/linux-riscv64": "0.25.2",
|
||||
"@esbuild/linux-s390x": "0.25.2",
|
||||
"@esbuild/linux-x64": "0.25.2",
|
||||
"@esbuild/netbsd-arm64": "0.25.2",
|
||||
"@esbuild/netbsd-x64": "0.25.2",
|
||||
"@esbuild/openbsd-arm64": "0.25.2",
|
||||
"@esbuild/openbsd-x64": "0.25.2",
|
||||
"@esbuild/sunos-x64": "0.25.2",
|
||||
"@esbuild/win32-arm64": "0.25.2",
|
||||
"@esbuild/win32-ia32": "0.25.2",
|
||||
"@esbuild/win32-x64": "0.25.2"
|
||||
}
|
||||
},
|
||||
"node_modules/folktest": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "git+https://git.kitsu.cafe/rowan/folktest.git#708d44f1215be33fcceba426029f44b4f963dbe5",
|
||||
"dev": true,
|
||||
"license": "GPL-3.0-or-later"
|
||||
},
|
||||
"node_modules/izuna": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "git+https://git.kitsu.cafe/rowan/izuna.git#e11a0870c27eeb5ea1a4ae3fedccca008eda15c2",
|
||||
"license": "GPL-3.0-or-later"
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
30
package.json
30
package.json
|
@ -1,27 +1,17 @@
|
|||
{
|
||||
"name": "kojima",
|
||||
"type": "module",
|
||||
"author": "Rowan <rowan@kitsu.cafe> (https://kitsu.cafe)",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"version": "2.0.0",
|
||||
"main": "src/index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "./tests/index.js",
|
||||
"build": "esbuild ./src/index.js --bundle --outfile=./dist/index.js"
|
||||
"test": "node --test"
|
||||
},
|
||||
"keywords": [
|
||||
"functional",
|
||||
"functional programming",
|
||||
"fp",
|
||||
"monad"
|
||||
],
|
||||
"license": "GPL-3.0-or-later",
|
||||
"devDependencies": {
|
||||
"folktest": "git+https://git.kitsu.cafe/rowan/folktest.git"
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@git.kitsu.cafe:rowan/kojima.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.2",
|
||||
"izuna": "git+https://git.kitsu.cafe/rowan/izuna.git",
|
||||
"typescript": "^5.8.2"
|
||||
}
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": ""
|
||||
}
|
||||
|
|
|
@ -1,177 +0,0 @@
|
|||
import { Algebra, Monad } from './interfaces.js'
|
||||
|
||||
/** @import { Applicative, ApplicativeTypeRef, Apply, Chain, Functor, Morphism } from './types.js' */
|
||||
|
||||
const Interfaces = Algebra(Monad)
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {Applicative<T> & (Pure<T> | Impure<T>)} Free
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @implements {Applicative<T>}
|
||||
*/
|
||||
export class Pure extends Interfaces {
|
||||
#value
|
||||
|
||||
/**
|
||||
* @param {T} value
|
||||
*/
|
||||
constructor(value) {
|
||||
super()
|
||||
this.#value = value
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {T} value
|
||||
*/
|
||||
static of(value) {
|
||||
return liftF(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* @template U
|
||||
* @type {Chain<T>['chain']}
|
||||
* @param {Morphism<T, Pure<U>>} f
|
||||
* @returns {Pure<U>}
|
||||
*/
|
||||
chain(f) {
|
||||
return f(this.#value)
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Functor<T>['map']}
|
||||
*/
|
||||
map(f) {
|
||||
return this.chain(x => pure(f(x)))
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @template U
|
||||
* @type {Apply<T>['ap']}
|
||||
* @param {Free<Morphism<T, U>>} b
|
||||
* @returns {Free<U>}
|
||||
*/
|
||||
ap(b) {
|
||||
return /** @type {Free<U>} */ (b.chain(f =>
|
||||
/** @type {Chain<U>} */(this.map(f))
|
||||
))
|
||||
}
|
||||
|
||||
interpret() {
|
||||
return this.#value
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `Pure(${this.#value})`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @implements {Applicative<T>}
|
||||
*/
|
||||
export class Impure extends Interfaces {
|
||||
#value
|
||||
#next
|
||||
|
||||
/**
|
||||
* @param {T} value
|
||||
* @param {(value: T) => Free<any>} next
|
||||
*/
|
||||
constructor(value, next) {
|
||||
super()
|
||||
this.#value = value
|
||||
this.#next = next
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {T} value
|
||||
*/
|
||||
static of(value) {
|
||||
return liftF(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* @template U
|
||||
* @type {Chain<T>['chain']}
|
||||
* @param {Morphism<T, Free<U>>} f
|
||||
* @returns {Free<T>}
|
||||
*/
|
||||
chain(f) {
|
||||
return /** @type {Free<T>} */ (impure(
|
||||
this.#value,
|
||||
x => /** @type {Free<U>} */(this.#next(x).chain(f))
|
||||
))
|
||||
}
|
||||
|
||||
/**
|
||||
* @template U
|
||||
* @type {Functor<T>['map']}
|
||||
* @param {Morphism<T, U>} f
|
||||
* @returns {Free<T>}
|
||||
*/
|
||||
map(f) {
|
||||
return /** @type {Free<T>} */ (impure(
|
||||
this.#value,
|
||||
x => /** @type Free<U>} */(this.#next(x).map(f))
|
||||
))
|
||||
}
|
||||
|
||||
/**
|
||||
* @template U
|
||||
* @type {Apply<T>['ap']}
|
||||
* @param {Free<Morphism<T, U>>} b
|
||||
* @returns {Free<T>}
|
||||
*/
|
||||
ap(b) {
|
||||
return /** @type {Free<T>} */ (impure(
|
||||
this.#value,
|
||||
x => /** @type {Free<U>} */(b.chain(f =>
|
||||
/** @type {Free<U>} */(this.#next(x).map(f)))
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
interpret() {
|
||||
return this.#next(this.#value).interpret()
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `Impure(${this.#value}, ${this.#next})`
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {T} value
|
||||
*/
|
||||
export const pure = value => new Pure(value)
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {T} x
|
||||
* @param {(value: T) => Free<any>} f
|
||||
*/
|
||||
export const impure = (x, f) => new Impure(x, f)
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {T} value
|
||||
*/
|
||||
export const liftF = value => impure(value, pure)
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @type {ApplicativeTypeRef<T, Free<T>>}
|
||||
*/
|
||||
export const TypeRef = Pure
|
||||
TypeRef.constructor['of'] = Pure
|
||||
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
import { Algebra, Comonad, Monad } from './interfaces.js'
|
||||
|
||||
/** @import { Apply, Functor, Morphism } from './types.js' */
|
||||
|
||||
/** @template T */
|
||||
export class Identity extends Algebra(Monad, Comonad) {
|
||||
#value
|
||||
|
||||
/**
|
||||
* @param {T} value
|
||||
*/
|
||||
constructor(value) {
|
||||
super()
|
||||
|
||||
this.#value = value
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {T} value
|
||||
*/
|
||||
static of(value) {
|
||||
return id(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* @template U
|
||||
* @type {Functor<T>['map']}
|
||||
* @param {Morphism<T, U>} f
|
||||
* @returns {Identity<U>}
|
||||
*/
|
||||
map(f) {
|
||||
return id(f(this.#value))
|
||||
}
|
||||
|
||||
/**
|
||||
* @template U
|
||||
* @type {Apply<T>['ap']}
|
||||
* @param {Apply<Morphism<T, U>>} b
|
||||
* @returns {Apply<U>}
|
||||
*/
|
||||
ap(b) {
|
||||
return /** @type {Apply<U>} */ (b.map(f => f(this.#value)))
|
||||
}
|
||||
|
||||
/**
|
||||
* @template U
|
||||
* @param {Morphism<T, Identity<U>>} f
|
||||
*/
|
||||
chain(f) {
|
||||
return f(this.#value)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(value: Identity<T>) => T} f
|
||||
*/
|
||||
extend(f) {
|
||||
return id(f(this))
|
||||
}
|
||||
|
||||
extract() {
|
||||
return this.#value
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `Identity(${this.#value})`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {T} value
|
||||
*/
|
||||
export const id = value => new Identity(value)
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
export { TypeRef as Option, Some, None, some, none } from './option.js'
|
||||
export { TypeRef as Result, Ok, Err, ok, err } from './result.js'
|
||||
export { TypeRef as Free, liftF } from './free.js'
|
||||
export { List, list } from './list.js'
|
||||
export { IO } from './io.js'
|
||||
export { Reader } from './reader.js'
|
||||
|
|
@ -1,388 +0,0 @@
|
|||
import { mix, Mixin } from '../mixin.js'
|
||||
|
||||
/** @import { MixinFunction } from '../mixin.js' */
|
||||
/** @import { Fn } from './types.js' */
|
||||
|
||||
export const ProtectedConstructor = Symbol('ProtectedConstructor')
|
||||
export class ProtectedConstructorError extends Error {
|
||||
/** @param {string} name */
|
||||
constructor(name) {
|
||||
super(`ProtectedConstructorError: ${name} cannot be directly constructed`)
|
||||
}
|
||||
}
|
||||
|
||||
class Derivations {
|
||||
#inner
|
||||
|
||||
/**
|
||||
* @param {Iterable<readonly [any, any]>} iterable
|
||||
*/
|
||||
constructor(iterable) {
|
||||
this.#inner = new Map(iterable)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Iterable<any>} keys
|
||||
* @param {any} value
|
||||
* @returns {this}
|
||||
*/
|
||||
set(keys, value) {
|
||||
this.#inner.set(new Set(keys), value)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Iterable<any>} keys
|
||||
* @returns {boolean}
|
||||
*/
|
||||
has(keys) {
|
||||
return this.#inner.has(new Set(keys))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Iterable<any>} keys
|
||||
*/
|
||||
get(keys) {
|
||||
return this.#inner.get(new Set(keys))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Iterable<any>} keys
|
||||
*/
|
||||
delete(keys) {
|
||||
return this.#inner.delete(new Set(keys))
|
||||
}
|
||||
}
|
||||
|
||||
export class NotImplementedError extends Error {
|
||||
/** @param {string} name */
|
||||
constructor(name) {
|
||||
super(`${name} is not implemented`)
|
||||
}
|
||||
}
|
||||
|
||||
/** @typedef {(mixin: MixinFunction) => MixinFunction} MixinWrapper */
|
||||
|
||||
/**
|
||||
* @enum {number}
|
||||
*/
|
||||
const MethodType = Object.freeze({
|
||||
Instance: 0,
|
||||
Static: 1
|
||||
})
|
||||
|
||||
class Method {
|
||||
#name
|
||||
|
||||
/** @type {MethodType} */
|
||||
#type = MethodType.Instance
|
||||
|
||||
/** @type {Fn} */
|
||||
#implementation
|
||||
|
||||
/**
|
||||
* @param {string | Method} name
|
||||
*/
|
||||
constructor(name) {
|
||||
if (name instanceof Method) {
|
||||
return name
|
||||
}
|
||||
|
||||
this.#name = name
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string | Method} value
|
||||
*/
|
||||
static from(value) {
|
||||
return new Method(value)
|
||||
}
|
||||
|
||||
isInstance() {
|
||||
this.#type = MethodType.Instance
|
||||
return this
|
||||
}
|
||||
|
||||
isStatic() {
|
||||
this.#type = MethodType.Static
|
||||
return this
|
||||
}
|
||||
|
||||
/** @param {Fn} f */
|
||||
implementation(f) {
|
||||
this.#implementation = f
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} interfaceName
|
||||
*/
|
||||
_defaultImplementation(interfaceName) {
|
||||
const err = new NotImplementedError(
|
||||
`${interfaceName}::${this.#name}`
|
||||
)
|
||||
|
||||
return function() { throw err }
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Function} target
|
||||
*/
|
||||
_getInstallationPoint(target) {
|
||||
switch (this.#type) {
|
||||
case 0: return Object.getPrototypeOf(target)
|
||||
case 1: return target
|
||||
default: return target
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Interface} builder
|
||||
* @param {Function} target
|
||||
*/
|
||||
implement(builder, target) {
|
||||
const impl = this.#implementation || this._defaultImplementation(builder.name)
|
||||
|
||||
this._getInstallationPoint(target)[this.#name] = impl
|
||||
}
|
||||
}
|
||||
|
||||
const Implementations = Symbol()
|
||||
|
||||
class Interface {
|
||||
#name
|
||||
|
||||
/** @type {MixinFunction} */
|
||||
#mixin
|
||||
|
||||
/** @type {Set<Method>} */
|
||||
#methods = new Set()
|
||||
|
||||
/** @type {Set<Interface>} */
|
||||
#interfaces = new Set()
|
||||
|
||||
/** @param {string} name */
|
||||
constructor(name) {
|
||||
this.#name = name
|
||||
this.#mixin = Mixin(this.build.bind(this))
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.#name
|
||||
}
|
||||
|
||||
findInterfaces(type) {
|
||||
let interfaces = new Set()
|
||||
let current = type
|
||||
|
||||
while (current != null) {
|
||||
interfaces = new Set(current[Implementations]).union(interfaces)
|
||||
current = Object.getPrototypeOf(current)
|
||||
}
|
||||
|
||||
return interfaces
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ name: string }} type
|
||||
* @returns {boolean}
|
||||
*/
|
||||
implementedBy(type) {
|
||||
return this.findInterfaces(type).has(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {...(PropertyKey | Method)} methods
|
||||
* @returns {this}
|
||||
*/
|
||||
specifies(...methods) {
|
||||
this.#methods = new Set(
|
||||
methods
|
||||
.map(Method.from)
|
||||
.concat(...this.#methods)
|
||||
)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {...Interface} interfaces
|
||||
* @returns {this}
|
||||
*/
|
||||
extends(...interfaces) {
|
||||
this.#interfaces = new Set(interfaces.concat(...this.#interfaces))
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {MixinFunction}
|
||||
*/
|
||||
asMixin() {
|
||||
return this.#mixin
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {FunctionConstructor} base
|
||||
*/
|
||||
build(base) {
|
||||
const interfaces = [...this.#interfaces.values()]
|
||||
const mixins = interfaces.map(x => x.asMixin())
|
||||
|
||||
const Interfaces = mix(base).with(...mixins)
|
||||
|
||||
const Algebra = class extends Interfaces { }
|
||||
|
||||
for (const method of this.#methods) {
|
||||
method.implement(this, Algebra)
|
||||
}
|
||||
|
||||
const prototype = Object.getPrototypeOf(Algebra)
|
||||
prototype[Implementations] = new Set(interfaces)
|
||||
|
||||
return Algebra
|
||||
}
|
||||
}
|
||||
|
||||
export class BaseSet { }
|
||||
|
||||
/**
|
||||
* @param {PropertyKey} key
|
||||
* @param {object} obj
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const hasOwn = (key, obj) => Object.hasOwn(obj, key)
|
||||
|
||||
/**
|
||||
* @param {Function} left
|
||||
* @param {Function} right
|
||||
*/
|
||||
export const EitherOf = (left, right) => mix(left).with(l => right(l))
|
||||
|
||||
/**
|
||||
* @param {string} methodName
|
||||
* @param {(...args: any[]) => any} f
|
||||
* @param {object} obj
|
||||
*/
|
||||
export const apply = (methodName, f, obj) => {
|
||||
if (hasOwn(methodName, obj)) {
|
||||
return obj[methodName](f)
|
||||
} else {
|
||||
return f(obj)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Function} base
|
||||
* @returns {(...algebras: Interface[]) => FunctionConstructor}
|
||||
*/
|
||||
export const AlgebraWithBase = base => (...algebras) => {
|
||||
return mix(base).with(...algebras.map(x => x.asMixin()))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {...Interface} algebras
|
||||
* @returns {FunctionConstructor}
|
||||
*/
|
||||
export const Algebra = AlgebraWithBase(BaseSet)
|
||||
|
||||
export const Setoid = new Interface('Setoid')
|
||||
.specifies('equals')
|
||||
|
||||
export const Ord = new Interface('Ord')
|
||||
.specifies('lte')
|
||||
|
||||
export const Semigroupoid = new Interface('Semigroupoid')
|
||||
.specifies('compose')
|
||||
|
||||
export const Category = new Interface('Category')
|
||||
.extends(Semigroupoid)
|
||||
.specifies(
|
||||
Method.from('id').isStatic()
|
||||
)
|
||||
|
||||
export const Semigroup = new Interface('Semigroup')
|
||||
.specifies('concat')
|
||||
|
||||
export const Monoid = new Interface('Monoid')
|
||||
.extends(Semigroup)
|
||||
.specifies(
|
||||
Method.from('empty').isStatic()
|
||||
)
|
||||
|
||||
export const Group = new Interface('Group')
|
||||
.extends(Monoid)
|
||||
.specifies('invert')
|
||||
|
||||
export const Filterable = new Interface('Filterable')
|
||||
.specifies('filter')
|
||||
|
||||
export const Functor = new Interface('Functor')
|
||||
.specifies('map')
|
||||
|
||||
export const Contravariant = new Interface('Contravariant')
|
||||
.specifies('contramap')
|
||||
|
||||
export const Apply = new Interface('Apply')
|
||||
.extends(Functor)
|
||||
.specifies('ap')
|
||||
|
||||
export const Applicative = new Interface('Applicative')
|
||||
.extends(Apply)
|
||||
.specifies(
|
||||
Method.from('of').isStatic()
|
||||
)
|
||||
|
||||
export const Alt = new Interface('Alt')
|
||||
.extends(Functor)
|
||||
.specifies('alt')
|
||||
|
||||
export const Plus = new Interface('Plus')
|
||||
.extends(Alt)
|
||||
.specifies(
|
||||
Method.from('zero').isStatic()
|
||||
)
|
||||
|
||||
export const Alternative = new Interface('Alternative')
|
||||
.extends(Applicative, Plus)
|
||||
|
||||
export const Foldable = new Interface('Foldable')
|
||||
.specifies('fold')
|
||||
|
||||
export const Traversable = new Interface('Traversable')
|
||||
.extends(Functor, Foldable)
|
||||
.specifies('traverse')
|
||||
|
||||
export const Chain = new Interface('Chain')
|
||||
.extends(Apply)
|
||||
.specifies(
|
||||
Method.from('chain')
|
||||
.implementation(function(f) {
|
||||
return f(this._value)
|
||||
})
|
||||
)
|
||||
|
||||
export const ChainRef = new Interface('ChainRec')
|
||||
.extends(Chain)
|
||||
.specifies(
|
||||
Method.from('chainRec').isStatic()
|
||||
)
|
||||
|
||||
export const Monad = new Interface('Monad')
|
||||
.extends(Applicative, Chain)
|
||||
|
||||
export const Extend = new Interface('Extend')
|
||||
.extends(Functor)
|
||||
.specifies('extend')
|
||||
|
||||
export const Comonad = new Interface('Comonad')
|
||||
.extends(Extend)
|
||||
.specifies('extract')
|
||||
|
||||
export const Bifunctor = new Interface('Bifunctor')
|
||||
.extends(Functor)
|
||||
.specifies('bimap')
|
||||
|
||||
export const Profunctor = new Interface('Profunctor')
|
||||
.extends(Functor)
|
||||
.specifies('promap')
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
import { Algebra, Monad } from './interfaces.js'
|
||||
|
||||
/** @import { Apply, Chain, Fn, Functor, Morphism } from './types.js' */
|
||||
|
||||
/** @template {Fn} T */
|
||||
export class IO extends Algebra(Monad) {
|
||||
_effect
|
||||
|
||||
/**
|
||||
* @param {T} effect
|
||||
*/
|
||||
constructor(effect) {
|
||||
super()
|
||||
this._effect = effect
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {Fn} T
|
||||
* @param {T} a
|
||||
*/
|
||||
static of(a) {
|
||||
return new IO(() => a)
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {Fn} U
|
||||
* @type {Chain<T>['chain']}
|
||||
* @param {Morphism<T, IO<U>>} f
|
||||
* @returns {IO<U>}
|
||||
*/
|
||||
chain(f) {
|
||||
return /** @type {IO<U>} */ (IO.of(() => f(this.run()).run()))
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {Fn} U
|
||||
* @type {Functor<T>['map']}
|
||||
* @param {Morphism<T, U>} f
|
||||
* @returns {IO<U>}
|
||||
*/
|
||||
map(f) {
|
||||
return /** @type {IO<U>} */ (IO.of(() => f(this.run())))
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {Fn} U
|
||||
* @type {Apply<T>['ap']}
|
||||
* @param {IO<Morphism<T, U>>} other
|
||||
* @returns {IO<U>}
|
||||
*/
|
||||
ap(other) {
|
||||
return /** @type {IO<U>} */ (IO.of((() => other.run()(this.run()))))
|
||||
}
|
||||
|
||||
run() {
|
||||
return this._effect()
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `IO(${this._effect})`
|
||||
}
|
||||
}
|
||||
|
|
@ -1,252 +0,0 @@
|
|||
import { Algebra, Comonad, Foldable, Monoid, Semigroup } from './interfaces.js'
|
||||
|
||||
/** @import { Apply, Chain, Foldable as FoldableT, Functor, Morphism, Semigroup as SemigroupT } from './types.js' */
|
||||
|
||||
const Interfaces = Algebra(Semigroup, Foldable, Monoid, Comonad)
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {Element<T> | Empty<T>} List
|
||||
*/
|
||||
|
||||
/** @template T */
|
||||
class Empty extends Interfaces {
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
/**
|
||||
* @template U
|
||||
* @type {Chain<T>['chain']}
|
||||
* @this {Empty<U>}
|
||||
* @param {Morphism<T, List<U>>} _f
|
||||
* @returns {List<U>}
|
||||
*/
|
||||
chain(_f) {
|
||||
return /** @type {List<U>} */ (this)
|
||||
}
|
||||
|
||||
/**
|
||||
* @template U
|
||||
* @type {Functor<T>['map']}
|
||||
* @this {Empty<U>}
|
||||
* @param {Morphism<T, U>} _f
|
||||
* @returns {List<U>}
|
||||
*/
|
||||
map(_f) {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @template U
|
||||
* @type {Apply<T>['ap']}
|
||||
* @this {Empty<U>}
|
||||
* @param {List<Morphism<T, U>>} _b
|
||||
* @returns {List<U>}
|
||||
*/
|
||||
ap(_b) {
|
||||
return /** @type {List<U>} */ (this)
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {SemigroupT<T>['concat']}
|
||||
*/
|
||||
concat(b) {
|
||||
return b
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {FoldableT<T>['reduce']}
|
||||
*/
|
||||
reduce(_f, acc) {
|
||||
return acc
|
||||
}
|
||||
|
||||
count() {
|
||||
return 0
|
||||
}
|
||||
|
||||
/** @returns {this is Empty<T>} */
|
||||
isEmpty() {
|
||||
return true
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `List(Empty)`
|
||||
}
|
||||
}
|
||||
|
||||
/** @template T */
|
||||
class Element extends Interfaces {
|
||||
#head
|
||||
#tail
|
||||
/** @type {List<T>} */
|
||||
#cache
|
||||
|
||||
/**
|
||||
* @param {T} head
|
||||
* @param {() => List<T>} [tail]
|
||||
*/
|
||||
constructor(head, tail = List.empty) {
|
||||
super()
|
||||
this.#head = head
|
||||
this.#tail = tail
|
||||
this.#cache = null
|
||||
}
|
||||
/**
|
||||
* @template U
|
||||
* @type {Chain<T>['chain']}
|
||||
* @this {Element<T>}
|
||||
* @param {Morphism<T, List<U>>} f
|
||||
* @returns {List<U>}
|
||||
*/
|
||||
chain(f) {
|
||||
return /** @type {List<U>} */ (f(this.#head).concat(
|
||||
/** @type {never} */(this.tail().chain(f))
|
||||
))
|
||||
}
|
||||
|
||||
/**
|
||||
* @template U
|
||||
* @type {Functor<T>['map']}
|
||||
* @param {Morphism<T, U>} f
|
||||
* @returns {List<U>}
|
||||
*/
|
||||
map(f) {
|
||||
return new Element(
|
||||
f(this.#head),
|
||||
() => /** @type {List<U>} */(this.tail().map(f))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @template U
|
||||
* @type {Apply<T>['ap']}
|
||||
* @this {Element<T>}
|
||||
* @param {List<Morphism<T, U>>} b
|
||||
* @returns {List<U>}
|
||||
*/
|
||||
ap(b) {
|
||||
if (b.isEmpty()) {
|
||||
return List.empty()
|
||||
}
|
||||
|
||||
const head = /** @type {List<U>} */ (this.map(b.head()))
|
||||
const rest = /** @type {List<U>} */ (this.ap(b.tail()))
|
||||
return /** @type {List<U>} */ (head.concat(rest))
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {SemigroupT<T>['concat']}
|
||||
*/
|
||||
concat(b) {
|
||||
return new Element(
|
||||
this.#head,
|
||||
() => /** @type {List<T>} */(this.tail().concat(b))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {FoldableT<T>['reduce']}
|
||||
*/
|
||||
reduce(f, acc) {
|
||||
return this.tail().reduce(f, f(acc, this.#head))
|
||||
}
|
||||
|
||||
head() {
|
||||
return this.#head
|
||||
}
|
||||
|
||||
tail() {
|
||||
return this.#cache || (this.#cache = this.#tail())
|
||||
}
|
||||
|
||||
count() {
|
||||
return this.tail().count() + 1
|
||||
}
|
||||
|
||||
/** @returns {this is Empty<T>} */
|
||||
isEmpty() {
|
||||
return false
|
||||
}
|
||||
|
||||
toArray() {
|
||||
return this.reduce(
|
||||
reduceArray,
|
||||
[]
|
||||
)
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `List(${this.toArray()})`
|
||||
}
|
||||
}
|
||||
|
||||
class TypeRef {
|
||||
/**
|
||||
* @template T
|
||||
* @param {T} value
|
||||
* @returns {List<T>}
|
||||
*/
|
||||
static of(value) {
|
||||
return new Element(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {Iterable<T>} iterable
|
||||
* @returns {List<T>}
|
||||
*/
|
||||
static from(iterable) {
|
||||
const iterator = Iterator.from(iterable)
|
||||
return List.fromIterator(iterator)
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {Iterator<T>} iterator
|
||||
* @returns {List<T>}
|
||||
*/
|
||||
static fromIterator(iterator) {
|
||||
const next = iterator.next()
|
||||
|
||||
if (next.done) {
|
||||
return List.empty()
|
||||
} else {
|
||||
return new Element(next.value, () => List.fromIterator(iterator))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {T} head
|
||||
* @param {List<T>} tail
|
||||
* @returns {List<T>}
|
||||
*/
|
||||
static cons(head, tail) {
|
||||
return new Element(head, () => tail)
|
||||
}
|
||||
|
||||
static empty() {
|
||||
return empty
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {T[]} acc
|
||||
* @param {T} x
|
||||
* @returns {T[]}
|
||||
*/
|
||||
const reduceArray = (acc, x) => acc.concat(x)
|
||||
|
||||
export const List = TypeRef
|
||||
|
||||
const empty = new Empty()
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {T} value
|
||||
*/
|
||||
export const list = value => List.of(value)
|
||||
|
|
@ -1,174 +0,0 @@
|
|||
import { id, Identity } from './identity.js'
|
||||
import { Algebra, Alternative, Foldable, Monad, Setoid } from './interfaces.js'
|
||||
|
||||
/** @import { Alt, Apply, Chain, Foldable as FoldableT, Functor, Morphism, Setoid as SetoidT } from './types.js' */
|
||||
|
||||
const Interfaces = Algebra(Setoid, Alternative, Monad, Foldable)
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {Some<T> | None<T>} Option
|
||||
*/
|
||||
|
||||
/** @template T */
|
||||
export class Some extends Interfaces {
|
||||
/** @type {T} */
|
||||
#value
|
||||
|
||||
/** @param {T} value */
|
||||
constructor(value) {
|
||||
super()
|
||||
|
||||
this.#value = value
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {SetoidT<T>['equals']}
|
||||
* @param {Some<T>} other
|
||||
*/
|
||||
equals(other) {
|
||||
if (other instanceof Some) { return false }
|
||||
const eq = /** @type {Some<T>} */ (other).chain(v => id(v === this.#value))
|
||||
return /** @type {Identity<boolean>} */ (eq).extract()
|
||||
}
|
||||
|
||||
/**
|
||||
* @template U
|
||||
* @type {Apply<T>['ap']}
|
||||
* @param {Some<Morphism<T, U>>} b
|
||||
* @returns {Some<U>}
|
||||
*/
|
||||
ap(b) {
|
||||
return /** @type {Some<U>} */ (b.chain(f =>
|
||||
/** @type {Some<U>} */(this.map(f))
|
||||
))
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Alt<T>['alt']}
|
||||
*/
|
||||
alt(b) {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Chain<T>['chain']}
|
||||
*/
|
||||
chain(f) {
|
||||
return f(this.#value)
|
||||
}
|
||||
|
||||
/**
|
||||
* @template U
|
||||
* @type {Functor<T>['map']}
|
||||
* @param {Morphism<T, U>} f
|
||||
* @returns {Some<U>}
|
||||
*/
|
||||
map(f) {
|
||||
return /** @type {Some<U>} */ (this.chain(v => some(f(v))))
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Functor<T>['map']}
|
||||
*/
|
||||
then(f) {
|
||||
return this.map(f)
|
||||
}
|
||||
|
||||
/**
|
||||
* @template U
|
||||
* @type {FoldableT<T>['reduce']}
|
||||
* @param {(acc: U, value: T) => U} f
|
||||
* @param {U} init
|
||||
* @returns {U}
|
||||
*/
|
||||
reduce(f, init) {
|
||||
return f(init, this.#value)
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `Some(${this.#value})`
|
||||
}
|
||||
}
|
||||
|
||||
/** @template T */
|
||||
export class None extends Interfaces {
|
||||
/**
|
||||
* @type {SetoidT<T>['equals']}
|
||||
*/
|
||||
equals(other) {
|
||||
return other === none
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Apply<T>['ap']}
|
||||
* @returns {this}
|
||||
*/
|
||||
ap(_b) {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Alt<T>['alt']}
|
||||
*/
|
||||
alt(b) {
|
||||
return b
|
||||
}
|
||||
|
||||
/**
|
||||
* @template U
|
||||
* @type {Chain<T>['chain']}
|
||||
* @param {Morphism<T, U>} _f
|
||||
* @returns {this}
|
||||
*/
|
||||
chain(_f) {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @template U
|
||||
* @type {Functor<T>['map']}
|
||||
* @param {Morphism<T, U>} _f
|
||||
* @returns {this}
|
||||
*/
|
||||
map(_f) {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @template R
|
||||
* @type {Functor<T>['map']}
|
||||
* @param {Morphism<T, R>} _f
|
||||
* @returns {None}
|
||||
*/
|
||||
then(_f) {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @template U
|
||||
* @type {FoldableT<T>['reduce']}
|
||||
* @param {(acc: U, value: T) => U} _f
|
||||
* @param {U} init
|
||||
* @returns {U}
|
||||
*/
|
||||
reduce(_f, init) {
|
||||
return init
|
||||
}
|
||||
|
||||
toString() {
|
||||
return 'None'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {T} value
|
||||
*/
|
||||
export const some = value => new Some(value)
|
||||
export const none = new None()
|
||||
|
||||
export const TypeRef = some
|
||||
TypeRef.constructor['of'] = some
|
||||
TypeRef.constructor['zero'] = none
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
import { id } from 'izuna'
|
||||
import { Algebra, Monad } from './interfaces.js'
|
||||
|
||||
/** @import { ApplicativeTypeRef, Apply, Chain, Functor, InferredMorphism, Morphism } from './types.js' */
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {InferredMorphism<T>} ReaderFn
|
||||
*/
|
||||
|
||||
/** @template T */
|
||||
export class Reader extends Algebra(Monad) {
|
||||
#run
|
||||
|
||||
/** @param {InferredMorphism<T>} run */
|
||||
constructor(run) {
|
||||
super()
|
||||
|
||||
this.#run = run
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @type {ApplicativeTypeRef<T, Reader<T>>['of']}
|
||||
* @param {T} a
|
||||
* @returns {Reader<T>}
|
||||
*/
|
||||
static of(a) {
|
||||
return new Reader(/** @type {InferredMorphism<T>} */(_env => a))
|
||||
}
|
||||
|
||||
/** @template T */
|
||||
static ask() {
|
||||
return new Reader(/** @type {InferredMorphism<T>} */(id))
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Functor<T>['map']}
|
||||
* @param {InferredMorphism<T>} f
|
||||
* @returns {Reader<T>}
|
||||
*/
|
||||
map(f) {
|
||||
return /** @type {Reader<any>} */ (this.chain(value => Reader.of(f(value))))
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Chain<T>['chain']}
|
||||
* @param {InferredMorphism<T>} f
|
||||
* @returns {Reader<T>}
|
||||
*/
|
||||
chain(f) {
|
||||
return new Reader(env => {
|
||||
const result = this.#run(env)
|
||||
const next = f(result)
|
||||
return next.run(env)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @template U
|
||||
* @type {Apply<T>['ap']}
|
||||
* @param {Reader<Morphism<T, U>>} b
|
||||
* @returns {Reader<U>}
|
||||
*/
|
||||
ap(b) {
|
||||
return /** @type {Reader<U>} */ (b.chain(f =>
|
||||
/** @type {Reader<U>} */(this.map(f))
|
||||
))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {InferredMorphism<T>} f
|
||||
* @returns {Reader<T>}
|
||||
*/
|
||||
local(f) {
|
||||
return new Reader(/** @type {InferredMorphism<T>} */(env => this.#run(f(env))))
|
||||
}
|
||||
|
||||
/**
|
||||
* @template U
|
||||
* @param {T} env
|
||||
* @returns {U}
|
||||
*/
|
||||
run(env) {
|
||||
return this.#run(env)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,287 +0,0 @@
|
|||
import { id, Identity } from './identity.js'
|
||||
import { Algebra, Alternative, Bifunctor, Foldable, Monad, Setoid } from './interfaces.js'
|
||||
|
||||
/** @import { Alt, Apply, Chain, Functor, Bifunctor as BifunctorT, Foldable as FoldableT, Morphism, Setoid as SetoidT } from './types.js' */
|
||||
|
||||
const Interfaces = Algebra(Setoid, Alternative, Monad, Foldable, Bifunctor)
|
||||
|
||||
/**
|
||||
* @template T, E
|
||||
* @typedef {Ok<T, E> | Err<T, E>} Result
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T, E
|
||||
*/
|
||||
export class Ok extends Interfaces {
|
||||
/** @type {T} */
|
||||
#value
|
||||
|
||||
/**
|
||||
* @param {T} value
|
||||
* @constructs {Ok<T, E>}
|
||||
*/
|
||||
constructor(value) {
|
||||
super()
|
||||
|
||||
this.#value = value
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {SetoidT<T>['equals']}
|
||||
* @param {Result<T, E>} other
|
||||
* @returns {boolean}
|
||||
*/
|
||||
equals(other) {
|
||||
if (!(other instanceof Ok)) { return false }
|
||||
const eq = other.chain(v => (id(v === this.#value)))
|
||||
return /** @type {Identity<boolean>} */ (eq).extract()
|
||||
}
|
||||
|
||||
/** @returns {this is Ok<T, E>} */
|
||||
isOk() { return true }
|
||||
|
||||
/** @returns {this is Err<T, E>} */
|
||||
isErr() { return false }
|
||||
|
||||
/**
|
||||
* @type {Chain<T>['chain']}
|
||||
*/
|
||||
chain(f) {
|
||||
return f(this.#value)
|
||||
}
|
||||
|
||||
/**
|
||||
* @template E2
|
||||
* @type {Chain<E>['chain']}
|
||||
* @param {Morphism<E, E2>} _f
|
||||
* @returns {this}
|
||||
*/
|
||||
chainErr(_f) {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @template U
|
||||
* @type {Functor<T>['map']}
|
||||
* @param {Morphism<T, U>} f
|
||||
* @returns {Functor<U>}
|
||||
*/
|
||||
map(f) {
|
||||
return this.chain(v => ok(f(v)))
|
||||
}
|
||||
|
||||
/**
|
||||
* @template E2
|
||||
* @type {Functor<E>['map']}
|
||||
* @this {Result<T, E>}
|
||||
* @param {Morphism<E, E2>} _f
|
||||
* @returns {Result<T, E2>}
|
||||
*/
|
||||
mapErr(_f) {
|
||||
return /** @type {never} */ (this)
|
||||
}
|
||||
|
||||
/**
|
||||
* @template U
|
||||
* @type {Apply<T>['ap']}
|
||||
* @param {Apply<Morphism<T, U>>} b
|
||||
* @returns {Result<U, E>}
|
||||
*/
|
||||
ap(b) {
|
||||
return /** @type {Result<U, E>} */ (this.chain(v =>
|
||||
/** @type {Chain<U>} */(b.map(f => f(v)))
|
||||
))
|
||||
}
|
||||
|
||||
/**
|
||||
* @type Alt<T>['alt']
|
||||
*/
|
||||
alt(_b) {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @template U
|
||||
* @borrows {Result~map}
|
||||
* @param {Morphism<T, U>} f
|
||||
*/
|
||||
then(f) {
|
||||
return this.map(f)
|
||||
}
|
||||
|
||||
/**
|
||||
* @template R
|
||||
* @param {Morphism<E, R>} _f
|
||||
* @returns {this}
|
||||
*/
|
||||
catch(_f) {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @template U
|
||||
* @type {FoldableT<T>['reduce']}
|
||||
* @param {(acc: U, value: T) => U} f
|
||||
* @param {U} init
|
||||
* @returns {U}
|
||||
*/
|
||||
reduce(f, init) {
|
||||
return f(init, this.#value)
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T2, E2
|
||||
* @type {BifunctorT<T, E>['bimap']}
|
||||
* @param {Morphism<T, T2>} f
|
||||
* @param {Morphism<E, E2>} _g
|
||||
* @returns {Result<T2, E2>}
|
||||
*/
|
||||
bimap(f, _g) {
|
||||
return /** @type {Result<T2, E2>} */ (this.map(f))
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `Ok(${this.#value})`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T, E
|
||||
*/
|
||||
export class Err extends Interfaces {
|
||||
/** @type {E} */
|
||||
#value
|
||||
|
||||
/**
|
||||
* @param {E} value
|
||||
* @constructs {Err<T, E>}
|
||||
*/
|
||||
constructor(value) {
|
||||
super()
|
||||
this.#value = value
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {SetoidT<T>['equals']}
|
||||
* @param {Err<T, E>} other
|
||||
* @returns {boolean}
|
||||
*/
|
||||
equals(other) {
|
||||
if (!(other instanceof Err)) { return false }
|
||||
const eq = other.chainErr(v => id(v === this.#value))
|
||||
return /** @type {Identity<boolean>} */ (eq).extract()
|
||||
}
|
||||
|
||||
/** @returns {this is Ok<T, E>} */
|
||||
isOk() { return false }
|
||||
|
||||
/** @returns {this is Err<T, E>} */
|
||||
isErr() { return true }
|
||||
|
||||
/**
|
||||
* @type {Chain<T>['chain']}
|
||||
* @returns {this}
|
||||
*/
|
||||
chain(_f) {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @template E2
|
||||
* @type {Chain<E>['chain']}
|
||||
* @param {Morphism<E, Result<T, E2>>} f
|
||||
* @returns {Result<T, E2>}
|
||||
*/
|
||||
chainErr(f) {
|
||||
return f(this.#value)
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Functor<T>['map']}
|
||||
* @returns {this}
|
||||
*/
|
||||
map(_f) {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @template E2
|
||||
* @type {Functor<E>['map']}
|
||||
* @param {Morphism<E, E2>} f
|
||||
* @returns {Result<T, E2>}
|
||||
*/
|
||||
mapErr(f) {
|
||||
return /** @type {Result<T, E2>} */ (this.bimap(id, f))
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Functor<T>['map']}
|
||||
* @returns {this}
|
||||
*/
|
||||
then(_f) {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @template R
|
||||
* @type {Functor<E>['map']}
|
||||
* @param {Morphism<E, R>} f
|
||||
* @returns {Err<T, R>}
|
||||
*/
|
||||
catch(f) {
|
||||
return new Err(f(this.#value))
|
||||
}
|
||||
|
||||
/**
|
||||
* @type Alt<T>['alt']
|
||||
*/
|
||||
alt(b) {
|
||||
return b
|
||||
}
|
||||
|
||||
/**
|
||||
* @template U
|
||||
* @type {FoldableT<T>['reduce']}
|
||||
* @param {(acc: U, value: T) => U} _f
|
||||
* @param {U} init
|
||||
* @returns {U}
|
||||
*/
|
||||
reduce(_f, init) {
|
||||
return init
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T2, E2
|
||||
* @type {BifunctorT<T, E>['bimap']}
|
||||
* @param {Morphism<T, T2>} _f
|
||||
* @param {Morphism<E, E2>} g
|
||||
* @returns {Result<T2, E2>}
|
||||
*/
|
||||
bimap(_f, g) {
|
||||
return /** @type {Result<T2, E2>} */ (err(g(this.#value)))
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `Err(${this.#value})`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T, E
|
||||
* @param {T} v
|
||||
* @returns {Ok<T, E>}
|
||||
*/
|
||||
export const ok = v => new Ok(v)
|
||||
|
||||
/**
|
||||
* @template T, E
|
||||
* @param {E} e
|
||||
* @returns {Err<T, E>}
|
||||
*/
|
||||
export const err = e => new Err(e)
|
||||
|
||||
export const TypeRef = ok
|
||||
TypeRef.constructor['of'] = ok
|
||||
TypeRef.constructor['zero'] = err
|
||||
|
|
@ -1,222 +0,0 @@
|
|||
export default {}
|
||||
|
||||
/**
|
||||
* @template T, R
|
||||
* @typedef {(value: T) => R} Morphism
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {<R>(value: T) => R} InferredMorphism
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {(value: T) => boolean} Predicate
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {{
|
||||
equals: (b: Setoid<T>) => boolean
|
||||
* }} Setoid
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {{
|
||||
lte: (b: Ord<T>) => boolean
|
||||
* }} Ord
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T, U
|
||||
* @typedef {*} Semigroupoid
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template I, J
|
||||
* @typedef {{
|
||||
compose: <K>(b: Semigroupoid<J, K>) => Semigroupoid<I, K>
|
||||
* }} Category
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T, U
|
||||
* @template {Category<T, U>} M
|
||||
* @typedef {{
|
||||
id: () => (value: T) => M
|
||||
* }} CategoryTypeRef
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {{
|
||||
concat: (b: Semigroup<T>) => Semigroup<T>
|
||||
* }} Semigroup
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef{Semigroup<T>} Monoid
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @template {Monoid<T>} M
|
||||
* @typedef {{
|
||||
empty: () => M
|
||||
* }} MonoidTypeRef
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {
|
||||
Monoid<T> &
|
||||
{ invert: () => Group<T> }
|
||||
* } Group
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {{
|
||||
filter: <U>(f: (acc: U, val: T) => U, init: U) => U
|
||||
* }} Filterable
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {{
|
||||
map: <U>(f: Morphism<T, U>) => Functor<U>
|
||||
* }} Functor
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {{
|
||||
contramap: <U>(f: Morphism<U, T>) => Contravariant<T>
|
||||
* }} Contravariant
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {
|
||||
Functor<T> &
|
||||
{ ap: <U>(f: Apply<Morphism<T, U>>) => Apply<U> }
|
||||
* } Apply
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {
|
||||
Functor<T> &
|
||||
Apply<T>
|
||||
* } Applicative
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @template {Applicative<T>} M
|
||||
* @typedef {{
|
||||
of: (value: T) => M
|
||||
* }} ApplicativeTypeRef
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {
|
||||
Functor<T> &
|
||||
{ alt: (b: Alt<T>) => Alt<T> }
|
||||
* } Alt
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {Alt<T>} Plus
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @template {Plus<T>} M
|
||||
* @typedef {
|
||||
Alt<T> &
|
||||
{ zero: () => M }
|
||||
* } PlusTypeRef
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {Applicative<T> & Plus<T>} Alternative
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @template {Applicative<T> & Plus<T>} M
|
||||
* @typedef {ApplicativeTypeRef<T, M> & PlusTypeRef<T, M>} AlternativeTypeRef
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {{
|
||||
reduce: <U>(f: (acc: U, val: T) => U, init: U) => U
|
||||
* }} Foldable
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {
|
||||
Functor<T> & Foldable<T> &
|
||||
{ traverse: <U>(A: ApplicativeTypeRef<U, Applicative<U>>, f: (val: T) => Applicative<U>) => Applicative<Traversable<U>> }
|
||||
* } Traversable
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {
|
||||
Apply<T> &
|
||||
{ chain: <U>(f: (value: T) => Chain<U>) => Chain<U> }
|
||||
* } Chain
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {Chain<T>} ChainRec
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {Functor<T> & Applicative<T> & Chain<T>} Monad
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @template {Monad<T>} M
|
||||
* @typedef {ApplicativeTypeRef<T, M>} MonadTypeDef
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {
|
||||
Functor<T> &
|
||||
{ extend: <U>(f: (val: Extend<T>) => U) => Extend<U>}
|
||||
* } Extend
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {
|
||||
Extend<T> &
|
||||
{ extract: () => T }
|
||||
* } Comonad<T>
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template A, X
|
||||
* @typedef {
|
||||
Functor<A> &
|
||||
{ bimap: <B, Y>(f: Morphism<A, B>, g: Morphism<X, Y>) => Bifunctor<B, Y> }
|
||||
* } Bifunctor
|
||||
*/
|
||||
|
||||
/** @typedef {(...args: any[]) => any} Fn */
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
/**
|
||||
* @template T, U
|
||||
* @param {(acc: U, value: T) => U} f
|
||||
* @param {U} acc
|
||||
*/
|
||||
export const Reducer = (f, acc) => {
|
||||
function Const(value) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
Const.of = function() { return new Const(acc) }
|
||||
Const.prototype.map = function() { return this }
|
||||
Const.prototype.ap = function(b) {
|
||||
return new Const(f(b.value, this.value))
|
||||
}
|
||||
return Const
|
||||
}
|
8
src/alias.js
Normal file
8
src/alias.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { Method } from "./specification/index.js"
|
||||
|
||||
export const alias = (type, aliases) => {
|
||||
Object.entries(aliases).forEach(([method, impl]) => {
|
||||
Method.from(impl).bind(type, method)
|
||||
})
|
||||
}
|
||||
|
7
src/common.js
Normal file
7
src/common.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
/**
|
||||
* @template T
|
||||
* @param {T} value
|
||||
* @returns {T}
|
||||
*/
|
||||
export const id = value => value
|
||||
|
8
src/error.js
Normal file
8
src/error.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
export class UnwrapError extends Error {
|
||||
name = 'UnwrapError'
|
||||
|
||||
constructor(desc) {
|
||||
super(`UnwrapError: ${desc}`)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
export * from './algebra/index.js'
|
||||
|
256
src/mixin.js
256
src/mixin.js
|
@ -1,256 +0,0 @@
|
|||
/**
|
||||
* mixwith.js
|
||||
* Author: Justin Fagnani (https://github.com/justinfagnani/)
|
||||
* https://github.com/justinfagnani/mixwith.js
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
// used by apply() and isApplicationOf()
|
||||
const _appliedMixin = '__mixwith_appliedMixin'
|
||||
|
||||
/**
|
||||
* A function that returns a subclass of its argument.
|
||||
*
|
||||
* @example
|
||||
* const M = (superclass) => class extends superclass {
|
||||
* getMessage() {
|
||||
* return "Hello"
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @typedef {Function} MixinFunction
|
||||
* @param {Function} superclass
|
||||
* @return {Function} A subclass of `superclass`
|
||||
*/
|
||||
|
||||
/**
|
||||
* Applies `mixin` to `superclass`.
|
||||
*
|
||||
* `apply` stores a reference from the mixin application to the unwrapped mixin
|
||||
* to make `isApplicationOf` and `hasMixin` work.
|
||||
*
|
||||
* This function is usefull for mixin wrappers that want to automatically enable
|
||||
* {@link hasMixin} support.
|
||||
*
|
||||
* @example
|
||||
* const Applier = (mixin) => wrap(mixin, (superclass) => apply(superclass, mixin))
|
||||
*
|
||||
* // M now works with `hasMixin` and `isApplicationOf`
|
||||
* const M = Applier((superclass) => class extends superclass {})
|
||||
*
|
||||
* class C extends M(Object) {}
|
||||
* let i = new C()
|
||||
* hasMixin(i, M) // true
|
||||
*
|
||||
* @function
|
||||
* @param {Function} superclass A class or constructor function
|
||||
* @param {MixinFunction} mixin The mixin to apply
|
||||
* @return {Function} A subclass of `superclass` produced by `mixin`
|
||||
*/
|
||||
export const apply = (superclass, mixin) => {
|
||||
let application = mixin(superclass)
|
||||
application.prototype[_appliedMixin] = unwrap(mixin)
|
||||
return application
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` iff `proto` is a prototype created by the application of
|
||||
* `mixin` to a superclass.
|
||||
*
|
||||
* `isApplicationOf` works by checking that `proto` has a reference to `mixin`
|
||||
* as created by `apply`.
|
||||
*
|
||||
* @function
|
||||
* @param {Object} proto A prototype object created by {@link apply}.
|
||||
* @param {MixinFunction} mixin A mixin function used with {@link apply}.
|
||||
* @return {boolean} whether `proto` is a prototype created by the application of
|
||||
* `mixin` to a superclass
|
||||
*/
|
||||
export const isApplicationOf = (proto, mixin) =>
|
||||
proto.hasOwnProperty(_appliedMixin) && proto[_appliedMixin] === unwrap(mixin)
|
||||
|
||||
/**
|
||||
* Returns `true` iff `o` has an application of `mixin` on its prototype
|
||||
* chain.
|
||||
*
|
||||
* @function
|
||||
* @param {Object} o An object
|
||||
* @param {MixinFunction} mixin A mixin applied with {@link apply}
|
||||
* @return {boolean} whether `o` has an application of `mixin` on its prototype
|
||||
* chain
|
||||
*/
|
||||
export const hasMixin = (o, mixin) => {
|
||||
while (o != null) {
|
||||
if (isApplicationOf(o, mixin)) return true
|
||||
o = Object.getPrototypeOf(o)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
// used by wrap() and unwrap()
|
||||
const _wrappedMixin = '__mixwith_wrappedMixin'
|
||||
|
||||
/**
|
||||
* Sets up the function `mixin` to be wrapped by the function `wrapper`, while
|
||||
* allowing properties on `mixin` to be available via `wrapper`, and allowing
|
||||
* `wrapper` to be unwrapped to get to the original function.
|
||||
*
|
||||
* `wrap` does two things:
|
||||
* 1. Sets the prototype of `mixin` to `wrapper` so that properties set on
|
||||
* `mixin` inherited by `wrapper`.
|
||||
* 2. Sets a special property on `mixin` that points back to `mixin` so that
|
||||
* it can be retreived from `wrapper`
|
||||
*
|
||||
* @function
|
||||
* @param {MixinFunction} mixin A mixin function
|
||||
* @param {MixinFunction} wrapper A function that wraps {@link mixin}
|
||||
* @return {MixinFunction} `wrapper`
|
||||
*/
|
||||
export const wrap = (mixin, wrapper) => {
|
||||
Object.setPrototypeOf(wrapper, mixin)
|
||||
if (!mixin[_wrappedMixin]) {
|
||||
mixin[_wrappedMixin] = mixin
|
||||
}
|
||||
return wrapper
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwraps the function `wrapper` to return the original function wrapped by
|
||||
* one or more calls to `wrap`. Returns `wrapper` if it's not a wrapped
|
||||
* function.
|
||||
*
|
||||
* @function
|
||||
* @param {MixinFunction} wrapper A wrapped mixin produced by {@link wrap}
|
||||
* @return {MixinFunction} The originally wrapped mixin
|
||||
*/
|
||||
export const unwrap = (wrapper) => wrapper[_wrappedMixin] || wrapper
|
||||
|
||||
const _cachedApplications = '__mixwith_cachedApplications'
|
||||
|
||||
/**
|
||||
* Decorates `mixin` so that it caches its applications. When applied multiple
|
||||
* times to the same superclass, `mixin` will only create one subclass, memoize
|
||||
* it and return it for each application.
|
||||
*
|
||||
* Note: If `mixin` somehow stores properties its classes constructor (static
|
||||
* properties), or on its classes prototype, it will be shared across all
|
||||
* applications of `mixin` to a super class. It's reccomended that `mixin` only
|
||||
* access instance state.
|
||||
*
|
||||
* @function
|
||||
* @param {MixinFunction} mixin The mixin to wrap with caching behavior
|
||||
* @return {MixinFunction} a new mixin function
|
||||
*/
|
||||
export const Cached = (mixin) => wrap(mixin, (superclass) => {
|
||||
// Get or create a symbol used to look up a previous application of mixin
|
||||
// to the class. This symbol is unique per mixin definition, so a class will have N
|
||||
// applicationRefs if it has had N mixins applied to it. A mixin will have
|
||||
// exactly one _cachedApplicationRef used to store its applications.
|
||||
|
||||
let cachedApplications = superclass[_cachedApplications]
|
||||
if (!cachedApplications) {
|
||||
cachedApplications = superclass[_cachedApplications] = new Map()
|
||||
}
|
||||
|
||||
let application = cachedApplications.get(mixin)
|
||||
if (!application) {
|
||||
application = mixin(superclass)
|
||||
cachedApplications.set(mixin, application)
|
||||
}
|
||||
|
||||
return application
|
||||
})
|
||||
|
||||
/**
|
||||
* Decorates `mixin` so that it only applies if it's not already on the
|
||||
* prototype chain.
|
||||
*
|
||||
* @function
|
||||
* @param {MixinFunction} mixin The mixin to wrap with deduplication behavior
|
||||
* @return {MixinFunction} a new mixin function
|
||||
*/
|
||||
export const DeDupe = (mixin) => wrap(mixin, (superclass) =>
|
||||
(hasMixin(superclass.prototype, mixin))
|
||||
? superclass
|
||||
: mixin(superclass))
|
||||
|
||||
/**
|
||||
* Adds [Symbol.hasInstance] (ES2015 custom instanceof support) to `mixin`.
|
||||
*
|
||||
* @function
|
||||
* @param {MixinFunction} mixin The mixin to add [Symbol.hasInstance] to
|
||||
* @return {MixinFunction} the given mixin function
|
||||
*/
|
||||
export const HasInstance = (mixin) => {
|
||||
if (Symbol && Symbol.hasInstance && !mixin[Symbol.hasInstance]) {
|
||||
Object.defineProperty(mixin, Symbol.hasInstance, {
|
||||
value(o) {
|
||||
return hasMixin(o, mixin)
|
||||
},
|
||||
})
|
||||
}
|
||||
return mixin
|
||||
}
|
||||
|
||||
/**
|
||||
* A basic mixin decorator that applies the mixin with {@link apply} so that it
|
||||
* can be used with {@link isApplicationOf}, {@link hasMixin} and the other
|
||||
* mixin decorator functions.
|
||||
*
|
||||
* @function
|
||||
* @param {MixinFunction} mixin The mixin to wrap
|
||||
* @return {MixinFunction} a new mixin function
|
||||
*/
|
||||
export const BareMixin = (mixin) => wrap(mixin, (s) => apply(s, mixin))
|
||||
|
||||
/**
|
||||
* Decorates a mixin function to add deduplication, application caching and
|
||||
* instanceof support.
|
||||
*
|
||||
* @function
|
||||
* @param {MixinFunction} mixin The mixin to wrap
|
||||
* @return {MixinFunction} a new mixin function
|
||||
*/
|
||||
export const Mixin = (mixin) => DeDupe(Cached(BareMixin(mixin)))
|
||||
|
||||
/**
|
||||
* A fluent interface to apply a list of mixins to a superclass.
|
||||
*
|
||||
* ```javascript
|
||||
* class X extends mix(Object).with(A, B, C) {}
|
||||
* ```
|
||||
*
|
||||
* The mixins are applied in order to the superclass, so the prototype chain
|
||||
* will be: X->C'->B'->A'->Object.
|
||||
*
|
||||
* This is purely a convenience function. The above example is equivalent to:
|
||||
*
|
||||
* ```javascript
|
||||
* class X extends C(B(A(Object))) {}
|
||||
* ```
|
||||
*
|
||||
* @function
|
||||
* @param {Function} [superclass=Object]
|
||||
* @return {MixinBuilder}
|
||||
*/
|
||||
export const mix = (superclass) => new MixinBuilder(superclass)
|
||||
|
||||
class MixinBuilder {
|
||||
|
||||
constructor(superclass) {
|
||||
this.superclass = superclass || class { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies `mixins` in order to the superclass given to `mix()`.
|
||||
*
|
||||
* @param {Array.<MixinFunction>} mixins
|
||||
* @return {FunctionConstructor} a subclass of `superclass` with `mixins` applied
|
||||
*/
|
||||
with(...mixins) {
|
||||
return mixins.reduce((c, m) => m(c), this.superclass)
|
||||
}
|
||||
}
|
||||
|
450
src/option.js
Normal file
450
src/option.js
Normal file
|
@ -0,0 +1,450 @@
|
|||
import { id } from './common.js'
|
||||
import { UnwrapError } from './error.js'
|
||||
import { Result } from './result.js'
|
||||
import { implementor } from './specification/index.js'
|
||||
import { Alt, Applicative, Chain, Filterable, Functor, Ord, Setoid } from './specification/structures.js'
|
||||
import { FantasyLand } from './specification/fantasy-land.js'
|
||||
|
||||
export class Option {
|
||||
/**
|
||||
* @template T
|
||||
* @param {T} value
|
||||
* @returns {Some<T>}
|
||||
*/
|
||||
static some(value) {
|
||||
return new Some(value)
|
||||
}
|
||||
|
||||
/** @returns {None} */
|
||||
static none() {
|
||||
return None
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(value: T) => Option<T>} fn
|
||||
* @param {Option<T>} self
|
||||
* @returns {Option<T>}
|
||||
*/
|
||||
static chain(fn, self) {
|
||||
return self.bind(fn)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(value: T) => bool} fn
|
||||
* @param {Option<T>} self
|
||||
* @returns {Option<T>}
|
||||
*/
|
||||
static filter(predicate, self) {
|
||||
return self.filter(predicate)
|
||||
}
|
||||
|
||||
/**
|
||||
* @template V
|
||||
* @param {(value: T) => V} fn
|
||||
* @param {Option<T>} self
|
||||
* @returns {Option<V>}
|
||||
*/
|
||||
static map(fn, self) {
|
||||
return self.map(fn)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Option<T>} other
|
||||
* @param {Option<T>} self
|
||||
* @returns {Option<T>}
|
||||
*/
|
||||
static alt(other, self) {
|
||||
return self.alt(other)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Option<T>} other
|
||||
* @param {Option<T>} self
|
||||
* @returns {Option<T>}
|
||||
*/
|
||||
static equals(other, self) {
|
||||
return self.equals(other)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Option<T>} other
|
||||
* @param {Option<T>} self
|
||||
* @returns {Option<T>}
|
||||
*/
|
||||
static lte(other, self) {
|
||||
return self.lte(other)
|
||||
}
|
||||
}
|
||||
|
||||
/** @template T */
|
||||
export class Some extends Option {
|
||||
/** @type T */
|
||||
#value
|
||||
|
||||
/** @param {T} value */
|
||||
constructor(value) {
|
||||
super()
|
||||
this.#value = value
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {this is Some}
|
||||
*/
|
||||
isSome() {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {this is _None}
|
||||
*/
|
||||
isNone() {
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(value: T) => Option<T>} fn
|
||||
* @returns {Option<T>}
|
||||
*/
|
||||
bind(fn) {
|
||||
return fn(this.#value)
|
||||
}
|
||||
|
||||
/**
|
||||
* @template V
|
||||
* @param {(value: T) => V} fn
|
||||
* @returns {Option<V>}
|
||||
*/
|
||||
map(fn) {
|
||||
return new Some(fn(this.#value))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Option<T>} other
|
||||
* @returns {Option<T>}
|
||||
*/
|
||||
alt(_other) {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Some<T>} other
|
||||
* @returns {Some<T>}
|
||||
*/
|
||||
and(other) {
|
||||
return other
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {() => Option<T>} other
|
||||
* @returns {Option<T>}
|
||||
*/
|
||||
orElse(_fn) {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {(value: T) => bool} predicate
|
||||
* @returns {Option<T>}
|
||||
*/
|
||||
filter(predicate) {
|
||||
return predicate(this.#value) ? this : None
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Option<T>} other
|
||||
* @returns {bool}
|
||||
*/
|
||||
equals(other) {
|
||||
return other.isSome() && this.#value === other.#value
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Option<T>} other
|
||||
* @returns {bool}
|
||||
*/
|
||||
lte(other) {
|
||||
return other.isSome() && this.#value <= other.#value
|
||||
}
|
||||
|
||||
/**
|
||||
* @template [V=T]
|
||||
* @param {(acc: V, value: T) => V} reducer
|
||||
* @param {V} init
|
||||
* @returns {V}
|
||||
*/
|
||||
reduce(reducer, init) {
|
||||
return reducer(init, this.#value)
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Option<T>}
|
||||
*/
|
||||
flatten() {
|
||||
if (this.#value instanceof Option) {
|
||||
return this.bind(id)
|
||||
} else {
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template E
|
||||
* @param {E} _err
|
||||
* @returns {Result<T, E>}
|
||||
*/
|
||||
okOr(_err) {
|
||||
return Result.ok(this.#value)
|
||||
}
|
||||
|
||||
/**
|
||||
* @template E
|
||||
* @template {() => E} F
|
||||
* @param {F} _err
|
||||
* @returns {Result<T, E>}
|
||||
*/
|
||||
okOrElse(_err) {
|
||||
return Result.ok(this.#value)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(value: T) => void} fn
|
||||
* @returns {this}
|
||||
*/
|
||||
inspect(fn) {
|
||||
fn(this.#value)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {T}
|
||||
* @throws {UnwrapError}
|
||||
*/
|
||||
unwrap() {
|
||||
return this.#value
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {T} _value
|
||||
* @returns {T}
|
||||
*/
|
||||
unwrapOr(_value) {
|
||||
return this.unwrap()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {() => T} _fn
|
||||
* @returns {T}
|
||||
*/
|
||||
unwrapOrElse(_fn) {
|
||||
return this.unwrap()
|
||||
}
|
||||
|
||||
/** @returns {string} */
|
||||
toString() {
|
||||
return `Some(${this.#value})`
|
||||
}
|
||||
}
|
||||
|
||||
class _None extends Option {
|
||||
/**
|
||||
* @returns {this is Some}
|
||||
*/
|
||||
isSome() {
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {this is _None}
|
||||
*/
|
||||
isNone() {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @template {Option<T>} R
|
||||
* @param {(value: T) => R} fn
|
||||
* @returns {R}
|
||||
*/
|
||||
bind(_fn) {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @template V
|
||||
* @template {Option<T>} R
|
||||
* @param {(value: T) => V} fn
|
||||
* @returns {R}
|
||||
*/
|
||||
map(_fn) {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {Option<T>} other
|
||||
* @returns {Option<T>}
|
||||
*/
|
||||
alt(other) {
|
||||
return other
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {() => Option<T>} other
|
||||
* @returns {Option<T>}
|
||||
*/
|
||||
orElse(fn) {
|
||||
return fn()
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {Some<T>} _other
|
||||
* @returns {Some<T>}
|
||||
*/
|
||||
and(_other) {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {(value: T) => bool} _predicate
|
||||
* @returns {Option<T>}
|
||||
*/
|
||||
filter(_predicate) {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {Option<T>} other
|
||||
* @returns {bool}
|
||||
*/
|
||||
equals(other) {
|
||||
return other.isNone()
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {Option<T>} _other
|
||||
* @returns {bool}
|
||||
*/
|
||||
lte(_other) {
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T, [V=T]
|
||||
* @param {(acc: V, value: T) => V} _reducer
|
||||
* @param {V} init
|
||||
* @returns {V}
|
||||
*/
|
||||
reduce(_reducer, init) {
|
||||
return init
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @template {Option<T>} R
|
||||
* @returns {R}
|
||||
*/
|
||||
flatten() {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T, E
|
||||
* @template {Result<T, E>} R
|
||||
* @param {E} err
|
||||
* @returns {R}
|
||||
*/
|
||||
okOr(err) {
|
||||
return Result.err(err)
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T, E
|
||||
* @template {Result<T, E>} R
|
||||
* @template {() => E} F
|
||||
* @param {F} err
|
||||
* @returns {R}
|
||||
*/
|
||||
okOrElse(err) {
|
||||
return Result.err(err())
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {(value: T) => void} _fn
|
||||
* @returns {this}
|
||||
*/
|
||||
inspect(_fn) {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @returns {T}
|
||||
* @throws {UnwrapError}
|
||||
*/
|
||||
unwrap() {
|
||||
throw new UnwrapError('tried to unwrap None')
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {T} value
|
||||
* @returns {T}
|
||||
*/
|
||||
unwrapOr(value) {
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {() => T} fn
|
||||
* @returns {T}
|
||||
*/
|
||||
unwrapOrElse(fn) {
|
||||
return fn()
|
||||
}
|
||||
|
||||
/** @returns {string} */
|
||||
toString() {
|
||||
return 'None'
|
||||
}
|
||||
}
|
||||
|
||||
export const None = new _None()
|
||||
|
||||
implementor(Option)
|
||||
.implements(Applicative)
|
||||
.specifiedBy(FantasyLand)
|
||||
.with({ of: Option.some })
|
||||
|
||||
const structures = [Setoid, Ord, Functor, Chain, Filterable, Alt]
|
||||
|
||||
implementor(Some)
|
||||
.implements(...structures)
|
||||
.specifiedBy(FantasyLand)
|
||||
.with({
|
||||
chain: Some.prototype.bind,
|
||||
filter: Some.prototype.filter,
|
||||
map: Some.prototype.map,
|
||||
alt: Some.prototype.alt,
|
||||
equals: Some.prototype.equals,
|
||||
lte: Some.prototype.lte,
|
||||
})
|
||||
|
||||
implementor(_None)
|
||||
.implements(...structures)
|
||||
.specifiedBy(FantasyLand)
|
||||
.with({
|
||||
chain: _None.prototype.bind,
|
||||
filter: _None.prototype.filter,
|
||||
map: _None.prototype.map,
|
||||
alt: _None.prototype.alt,
|
||||
equals: _None.prototype.equals,
|
||||
lte: _None.prototype.lte,
|
||||
})
|
426
src/result.js
Normal file
426
src/result.js
Normal file
|
@ -0,0 +1,426 @@
|
|||
import { id } from './common.js'
|
||||
import { implementor } from './specification/index.js'
|
||||
import { Alt, Applicative, Chain, Foldable, Functor, Ord, Setoid } from './specification/structures.js'
|
||||
import { FantasyLand } from './specification/fantasy-land.js'
|
||||
import { UnwrapError } from './error.js'
|
||||
import { Option, None } from './option.js'
|
||||
|
||||
export class Result {
|
||||
/**
|
||||
* @template T
|
||||
* @param {T} value
|
||||
*/
|
||||
static ok(value) {
|
||||
return new Ok(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* @template E
|
||||
* @param {E} error
|
||||
*/
|
||||
static err(error) {
|
||||
return new Err(error)
|
||||
}
|
||||
|
||||
// static-land methods
|
||||
static chain(fn, self) {
|
||||
return self.bind(fn)
|
||||
}
|
||||
|
||||
static map(fn, self) {
|
||||
return self.map(fn)
|
||||
}
|
||||
|
||||
static alt(other, self) {
|
||||
return self.alt(other)
|
||||
}
|
||||
|
||||
static equals(other, self) {
|
||||
return self.equals(other)
|
||||
}
|
||||
|
||||
static lte(other, self) {
|
||||
return self.lte(other)
|
||||
}
|
||||
}
|
||||
|
||||
/** @template T */
|
||||
export class Ok extends Result {
|
||||
/** @type T */
|
||||
#value
|
||||
|
||||
/** @param {T} value */
|
||||
constructor(value) {
|
||||
super()
|
||||
this.#value = value
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {bool}
|
||||
*/
|
||||
isOk() {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {bool}
|
||||
*/
|
||||
isErr() {
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* @template E
|
||||
* @template {Result<T, E>} R
|
||||
* @param {(value: T) => R} fn
|
||||
* @returns {R}
|
||||
*/
|
||||
bind(fn) {
|
||||
return fn(this.#value)
|
||||
}
|
||||
|
||||
/**
|
||||
* @template V, E
|
||||
* @template {Result<V, E>} R
|
||||
* @param {(value: T) => V} fn
|
||||
* @returns {R}
|
||||
*/
|
||||
map(fn) {
|
||||
return Result.ok(fn(this.#value))
|
||||
}
|
||||
|
||||
/**
|
||||
* @template E
|
||||
* @param {Result<T, E>} other
|
||||
* @returns {Result<T, E>}
|
||||
*/
|
||||
and(other) {
|
||||
return other
|
||||
}
|
||||
|
||||
/**
|
||||
* @template E
|
||||
* @param {Result<T, E>} other
|
||||
* @returns {Result<T, E>}
|
||||
*/
|
||||
alt(_other) {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @template E, F
|
||||
* @param {(error: E) => Result<T, F>} other
|
||||
* @returns {Result<T, F>}
|
||||
*/
|
||||
orElse(_fn) {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @template E
|
||||
* @param {Result<T, E>} other
|
||||
* @returns {bool}
|
||||
*/
|
||||
equals(other) {
|
||||
return other.isOk() && this.#value === other.#value
|
||||
}
|
||||
|
||||
/**
|
||||
* @template E
|
||||
* @param {Result<T, E>} other
|
||||
* @returns {bool}
|
||||
*/
|
||||
lte(other) {
|
||||
return other.isOk() && this.#value <= other.#value
|
||||
}
|
||||
|
||||
/**
|
||||
* @template [V=T]
|
||||
* @param {(acc: V, value: T) => V} reducer
|
||||
* @param {V} init
|
||||
* @returns {V}
|
||||
*/
|
||||
reduce(reducer, init) {
|
||||
return reducer(init, this.#value)
|
||||
}
|
||||
|
||||
/**
|
||||
* @template E
|
||||
* @template {Result<T, E>} R
|
||||
* @returns {R}
|
||||
*/
|
||||
flatten() {
|
||||
if (this.#value instanceof Result) {
|
||||
return this.bind(id)
|
||||
} else {
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Option<T>}
|
||||
*/
|
||||
ok() {
|
||||
return Option.some(this.#value)
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Option<T>}
|
||||
*/
|
||||
err() {
|
||||
return None
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(value: T) => void} fn
|
||||
* @returns {this}
|
||||
*/
|
||||
inspect(fn) {
|
||||
fn(this.#value)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {T}
|
||||
* @throws {UnwrapError}
|
||||
*/
|
||||
unwrap() {
|
||||
return this.#value
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {E}
|
||||
* @throws {UnwrapError}
|
||||
*/
|
||||
unwrapErr() {
|
||||
throw new UnwrapError('tried to unwrapErr an Ok')
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {T} _value
|
||||
* @returns {T}
|
||||
*/
|
||||
unwrapOr(_value) {
|
||||
return this.unwrap()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {() => T} _fn
|
||||
* @returns {T}
|
||||
*/
|
||||
unwrapOrElse(_fn) {
|
||||
return this.unwrap()
|
||||
}
|
||||
|
||||
/** @returns {string} */
|
||||
toString() {
|
||||
return `Ok(${this.#value})`
|
||||
}
|
||||
}
|
||||
|
||||
/** @template E */
|
||||
export class Err extends Result {
|
||||
/** @type E */
|
||||
#error
|
||||
|
||||
/** @param {E} value */
|
||||
constructor(error) {
|
||||
super()
|
||||
this.#error = error
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {bool}
|
||||
*/
|
||||
isOk() {
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {bool}
|
||||
*/
|
||||
isErr() {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @template {Result<T, E>} R
|
||||
* @param {(value: T) => R} fn
|
||||
* @returns {R}
|
||||
*/
|
||||
bind(_fn) {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @template V, E
|
||||
* @template {Result<V, E>} R
|
||||
* @param {(value: T) => V} fn
|
||||
* @returns {R}
|
||||
*/
|
||||
map(_fn) {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {Result<T, E>} _other
|
||||
* @returns {Result<T, E>}
|
||||
*/
|
||||
and(_other) {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {Result<T, E>} other
|
||||
* @returns {Result<T, E>}
|
||||
*/
|
||||
alt(other) {
|
||||
return other
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T, F
|
||||
* @param {(error: E) => Result<T, F>} other
|
||||
* @returns {Result<T, F>}
|
||||
*/
|
||||
orElse(fn) {
|
||||
return fn(this.#error)
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {Result<T, E>} other
|
||||
* @returns {bool}
|
||||
*/
|
||||
equals(other) {
|
||||
return other.isErr() && this.#error === other.#error
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {Result<T, E>} other
|
||||
* @returns {bool}
|
||||
*/
|
||||
lte(other) {
|
||||
return other.isErr() && this.#error <= other.#error
|
||||
}
|
||||
|
||||
/**
|
||||
* @template [V=T]
|
||||
* @param {(acc: V, value: T) => V} _reducer
|
||||
* @param {V} init
|
||||
* @returns {V}
|
||||
*/
|
||||
reduce(_reducer, init) {
|
||||
return init
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @template {Result<T, E>} R
|
||||
* @returns {R}
|
||||
*/
|
||||
flatten() {
|
||||
if (this.#error instanceof Err) {
|
||||
return this.bind(id)
|
||||
} else {
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Option<T>}
|
||||
*/
|
||||
ok() {
|
||||
return None
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Option<T>}
|
||||
*/
|
||||
err() {
|
||||
return Option.some(this.#error)
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {(value: T) => void} _fn
|
||||
* @returns {this}
|
||||
*/
|
||||
inspect(_fn) {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @returns {T}
|
||||
* @throws {UnwrapError}
|
||||
*/
|
||||
unwrap() {
|
||||
throw new UnwrapError('tried to unwrap an Err')
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {E}
|
||||
* @throws {UnwrapError}
|
||||
*/
|
||||
unwrapErr() {
|
||||
return this.#error
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {T} value
|
||||
* @returns {T}
|
||||
*/
|
||||
unwrapOr(value) {
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {() => T} fn
|
||||
* @returns {T}
|
||||
*/
|
||||
unwrapOrElse(fn) {
|
||||
return fn()
|
||||
}
|
||||
|
||||
/** @returns {string} */
|
||||
toString() {
|
||||
return `Err(${this.#error})`
|
||||
}
|
||||
}
|
||||
|
||||
implementor(Result)
|
||||
.implements(Applicative)
|
||||
.specifiedBy(FantasyLand)
|
||||
.with({ of: Result.ok })
|
||||
|
||||
const structures = [Setoid, Ord, Functor, Chain, Alt, Foldable]
|
||||
|
||||
implementor(Ok)
|
||||
.implements(...structures)
|
||||
.specifiedBy(FantasyLand)
|
||||
.with({
|
||||
chain: Ok.prototype.bind,
|
||||
map: Ok.prototype.map,
|
||||
reduce: Ok.prototype.reduce,
|
||||
alt: Ok.prototype.alt,
|
||||
equals: Ok.prototype.equals,
|
||||
lte: Ok.prototype.lte,
|
||||
})
|
||||
|
||||
implementor(Err)
|
||||
.implements(...structures)
|
||||
.specifiedBy(FantasyLand)
|
||||
.with({
|
||||
chain: Err.prototype.bind,
|
||||
map: Err.prototype.map,
|
||||
reduce: Err.prototype.reduce,
|
||||
alt: Err.prototype.alt,
|
||||
equals: Err.prototype.equals,
|
||||
lte: Err.prototype.lte,
|
||||
})
|
19
src/specification/fantasy-land.js
Normal file
19
src/specification/fantasy-land.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { zip } from '../utils.js'
|
||||
import { Specification } from './index.js'
|
||||
import * as Structures from './structures.js'
|
||||
|
||||
const prefix = name => `fantasy-land/${name}`
|
||||
|
||||
const structures = Object.values(Structures).map(struct => {
|
||||
const methods = struct.methods.map(m => m.name)
|
||||
if (methods.length === 0) {
|
||||
return struct
|
||||
}
|
||||
|
||||
const overrides = methods.map(prefix)
|
||||
const schema = Object.fromEntries(zip(methods, overrides))
|
||||
return struct.with(schema)
|
||||
})
|
||||
|
||||
export const FantasyLand = new Specification('fantasy-land', structures)
|
||||
|
293
src/specification/index.js
Normal file
293
src/specification/index.js
Normal file
|
@ -0,0 +1,293 @@
|
|||
import { path } from '../utils.js'
|
||||
|
||||
export class Method {
|
||||
name
|
||||
method
|
||||
isStatic
|
||||
|
||||
constructor(name, method = name, { isStatic = false } = {}) {
|
||||
this.name = name
|
||||
this.method = method
|
||||
this.isStatic = isStatic
|
||||
}
|
||||
|
||||
static from(value, options) {
|
||||
if (value instanceof Method) {
|
||||
return value
|
||||
} else {
|
||||
return new Method(value, options)
|
||||
}
|
||||
}
|
||||
|
||||
static asStatic(value, options) {
|
||||
return new Method(value, { isStatic: true, ...options })
|
||||
}
|
||||
|
||||
#getBindTarget(target) {
|
||||
if (this.isStatic) {
|
||||
return target
|
||||
} else {
|
||||
return target.prototype
|
||||
}
|
||||
}
|
||||
|
||||
bind(target, impl) {
|
||||
this.#getBindTarget(target)[this.method] = impl
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new Method(this.name, this.method, { isStatic: this.isStatic })
|
||||
}
|
||||
}
|
||||
|
||||
export class Structure {
|
||||
name
|
||||
methods
|
||||
dependencies
|
||||
#methodsMap
|
||||
|
||||
constructor(name, methods = [], dependencies = []) {
|
||||
this.name = name
|
||||
this.dependencies = dependencies
|
||||
|
||||
this.methods = []
|
||||
this.#methodsMap = new Map()
|
||||
methods.forEach(m => this.hasMethod(m))
|
||||
}
|
||||
|
||||
hasMethod(method, options) {
|
||||
if (!(method instanceof Method)) {
|
||||
method = Method.from(method, options)
|
||||
}
|
||||
|
||||
|
||||
this.methods.push(method)
|
||||
this.#methodsMap.set(method.name, method)
|
||||
return this
|
||||
}
|
||||
|
||||
dependsOn(...algebras) {
|
||||
this.dependencies.push(...algebras)
|
||||
return this
|
||||
}
|
||||
|
||||
getMethod(name) {
|
||||
return this.#methodsMap.get(name)
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new Structure(
|
||||
this.name,
|
||||
this.methods.map(m => m.clone()),
|
||||
this.dependencies.slice())
|
||||
}
|
||||
|
||||
with(aliases) {
|
||||
const clonedStructure = this.clone()
|
||||
|
||||
if (!(aliases instanceof AliasMap)) {
|
||||
aliases = AliasMap.from(aliases)
|
||||
}
|
||||
|
||||
for (const [id, override] of aliases.schema.entries()) {
|
||||
const method = clonedStructure.getMethod(id)
|
||||
if (method) {
|
||||
method.method = override
|
||||
} else {
|
||||
console.warn(`Warning: Attempted to override method '${id}' not found in Structure '${this.name}'.`)
|
||||
}
|
||||
}
|
||||
|
||||
return clonedStructure
|
||||
}
|
||||
}
|
||||
|
||||
class DependencyGraph {
|
||||
#structures
|
||||
#methods
|
||||
|
||||
constructor(structures) {
|
||||
this.#structures = new Map()
|
||||
|
||||
for (const struct of structures) {
|
||||
this.#structures.set(struct.name, struct)
|
||||
}
|
||||
|
||||
this.#methods = new Map()
|
||||
this.#build()
|
||||
}
|
||||
|
||||
#build() {
|
||||
const visited = new Set()
|
||||
const stack = []
|
||||
|
||||
for (const structure of this.#structures.values()) {
|
||||
stack.push(structure)
|
||||
}
|
||||
|
||||
while (stack.length > 0) {
|
||||
const current = stack.pop()
|
||||
|
||||
if (visited.has(current.name)) {
|
||||
continue
|
||||
}
|
||||
visited.add(current.name)
|
||||
|
||||
for (const method of current.methods) {
|
||||
if (!this.#methods.has(method.name)) {
|
||||
this.#methods.set(method.name, method)
|
||||
}
|
||||
}
|
||||
|
||||
for (const dep of current.dependencies) {
|
||||
const resolvedDep = this.#structures.get(dep.name) || dep
|
||||
stack.push(resolvedDep)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getMethods() {
|
||||
return this.#methods
|
||||
}
|
||||
}
|
||||
|
||||
export class Specification {
|
||||
name
|
||||
structures
|
||||
#dependencies
|
||||
|
||||
constructor(name, structures) {
|
||||
this.name = name
|
||||
|
||||
if (structures instanceof Map) {
|
||||
this.structures = structures
|
||||
} else {
|
||||
this.structures = new Map()
|
||||
|
||||
for (const struct of structures) {
|
||||
if (!(struct instanceof Structure)) {
|
||||
throw new TypeError("Expected an array of Structure instances.")
|
||||
}
|
||||
this.structures.set(struct.name, struct)
|
||||
}
|
||||
}
|
||||
|
||||
this.#dependencies = new DependencyGraph(this.structures.values())
|
||||
}
|
||||
|
||||
implementedBy(target) {
|
||||
return new Implementor(target, this)
|
||||
}
|
||||
|
||||
getOverrides(structs) {
|
||||
return structs.map(s => this.structures.get(s.name))
|
||||
}
|
||||
|
||||
getAllMethods() {
|
||||
return this.#dependencies.getMethods()
|
||||
}
|
||||
}
|
||||
|
||||
export class Implementor {
|
||||
target
|
||||
structures = []
|
||||
specification
|
||||
|
||||
constructor(target, specification = undefined) {
|
||||
this.target = target
|
||||
this.specification = specification
|
||||
}
|
||||
|
||||
implements(...structures) {
|
||||
for (const struct of structures) {
|
||||
let structure
|
||||
|
||||
if (struct instanceof Structure) {
|
||||
structure = struct
|
||||
} else if (typeof struct === 'string') {
|
||||
structure = this.specification.getStructure(struct)
|
||||
if (!structure) {
|
||||
console.warn(`Warning: Structure '${struct}' not found in specification '${this.specification.name}'.`)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
throw new TypeError(`Expected Structure instance or string name, got ${typeof struct}.`)
|
||||
}
|
||||
|
||||
if (structure) {
|
||||
this.structures.push(structure)
|
||||
}
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
specifiedBy(specification) {
|
||||
this.specification = specification
|
||||
return this
|
||||
}
|
||||
|
||||
with(aliases) {
|
||||
if (!(aliases instanceof AliasMap)) {
|
||||
aliases = AliasMap.from(aliases)
|
||||
}
|
||||
|
||||
let methods
|
||||
if (this.structures.size === 0) {
|
||||
console.warn(`${this.target.name} is implementing ${this.specification.name} with no specified structures`)
|
||||
methods = this.specification.getMethods()
|
||||
} else {
|
||||
const overrides = this.specification.getOverrides(this.structures)
|
||||
methods = new DependencyGraph(overrides).getMethods()
|
||||
}
|
||||
|
||||
for (const [alias, impl] of aliases.schema.entries()) {
|
||||
const method = methods.get(alias)
|
||||
|
||||
if (method) {
|
||||
let bindMethod
|
||||
|
||||
if (typeof impl === 'function') {
|
||||
bindMethod = impl
|
||||
} else if (typeof impl === 'string') {
|
||||
bindMethod = path(impl, this.target)
|
||||
if (bindMethod == null || typeof bindMethod !== 'function') {
|
||||
throw new TypeError(`Could not resolve string alias '${impl}' for specification method '${alias}'`)
|
||||
}
|
||||
} else {
|
||||
throw new TypeError(`Alias '${alias}' has an invalid implementation value. Expected function or string.`)
|
||||
}
|
||||
|
||||
if (bindMethod != null) {
|
||||
method.bind(this.target, bindMethod)
|
||||
} else {
|
||||
console.warn(`Warning: No valid implementation found for alias '${alias}'.`)
|
||||
}
|
||||
} else {
|
||||
console.warn(`Warning: Alias '${alias}' does not correspond to any method in the specification.`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class AliasMap {
|
||||
schema
|
||||
|
||||
constructor(schema = new Map()) {
|
||||
this.schema = schema
|
||||
}
|
||||
|
||||
static from(iterable) {
|
||||
if (iterable instanceof Map) {
|
||||
return new AliasMap(iterable)
|
||||
} else if (Array.isArray(iterable) || typeof iterable[Symbol.iterator] == 'function') {
|
||||
return new AliasMap(new Map(iterable))
|
||||
} else if (typeof iterable === 'object') {
|
||||
return new AliasMap(new Map(Object.entries(iterable)))
|
||||
} else {
|
||||
throw new TypeError("can't make bind target from given schema")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const implementor = (target, specification = null) => new Implementor(target, specification)
|
0
src/specification/static-land.js
Normal file
0
src/specification/static-land.js
Normal file
91
src/specification/structures.js
Normal file
91
src/specification/structures.js
Normal file
|
@ -0,0 +1,91 @@
|
|||
import { Method, Structure } from './index.js'
|
||||
|
||||
const method = (name, isStatic = false) => new Method(name, name, { isStatic })
|
||||
|
||||
export const Setoid = new Structure('Setoid')
|
||||
.hasMethod(method('equals'))
|
||||
|
||||
export const Ord = new Structure('Ord')
|
||||
.hasMethod(method('lte'))
|
||||
.dependsOn(Setoid)
|
||||
|
||||
export const Semigroupoid = new Structure('Semigroupoid')
|
||||
.hasMethod(method('compose'))
|
||||
|
||||
export const Category = new Structure('Category')
|
||||
.hasMethod(method('id', true))
|
||||
.dependsOn(Semigroupoid)
|
||||
|
||||
export const Semigroup = new Structure('Semigroup')
|
||||
.hasMethod(method('concat'))
|
||||
|
||||
export const Monoid = new Structure('Monoid')
|
||||
.hasMethod(method('empty', true))
|
||||
.dependsOn(Semigroup)
|
||||
|
||||
export const Group = new Structure('Group')
|
||||
.hasMethod(method('invert'))
|
||||
.dependsOn(Monoid)
|
||||
|
||||
export const Foldable = new Structure('Foldable')
|
||||
.hasMethod(method('reduce'))
|
||||
|
||||
export const Functor = new Structure('Functor')
|
||||
.hasMethod(method('map'))
|
||||
|
||||
export const Traversable = new Structure('Traversable')
|
||||
.hasMethod(method('traverse'))
|
||||
.dependsOn(Foldable, Functor)
|
||||
|
||||
export const Profunctor = new Structure('Profunctor')
|
||||
.hasMethod(method('promap'))
|
||||
.dependsOn(Functor)
|
||||
|
||||
export const Alt = new Structure('Alt')
|
||||
.hasMethod(method('alt'))
|
||||
.dependsOn(Functor)
|
||||
|
||||
export const Plus = new Structure('Plus')
|
||||
.hasMethod(method('zero', true))
|
||||
.dependsOn(Alt)
|
||||
|
||||
export const Apply = new Structure('Apply')
|
||||
.hasMethod(method('ap'))
|
||||
.dependsOn(Functor)
|
||||
|
||||
export const Applicative = new Structure('Applicative')
|
||||
.hasMethod(method('of', true))
|
||||
.dependsOn(Apply)
|
||||
|
||||
export const Chain = new Structure('Chain')
|
||||
.hasMethod(method('chain'))
|
||||
.dependsOn(Apply)
|
||||
|
||||
export const ChainRec = new Structure('ChainRec')
|
||||
.hasMethod(method('chainRec'))
|
||||
.dependsOn(Chain)
|
||||
|
||||
export const Alternative = new Structure('Alternative')
|
||||
.dependsOn(Plus, Applicative)
|
||||
|
||||
export const Monad = new Structure('Monad')
|
||||
.dependsOn(Applicative, Chain)
|
||||
|
||||
export const Bifunctor = new Structure('Bifunctor')
|
||||
.hasMethod(method('bimap'))
|
||||
.dependsOn(Functor)
|
||||
|
||||
export const Extend = new Structure('Extend')
|
||||
.hasMethod(method('extend'))
|
||||
.dependsOn(Functor)
|
||||
|
||||
export const Comonad = new Structure('Comonad')
|
||||
.hasMethod(method('extract'))
|
||||
.dependsOn(Extend)
|
||||
|
||||
export const Contravariant = new Structure('Contravariant')
|
||||
.hasMethod(method('contramap'))
|
||||
|
||||
export const Filterable = new Structure('Filterable')
|
||||
.hasMethod(method('filter'))
|
||||
|
95
src/union.js
95
src/union.js
|
@ -1,95 +0,0 @@
|
|||
/**
|
||||
* @template R
|
||||
* @typedef {(...args: any[]) => R} Fn
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template R
|
||||
* @typedef {() => R} EmptyFn
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template R
|
||||
* @callback cata
|
||||
* @param {Record<string, Fn<R>> & Partial<Record<'_', EmptyFn<R>>>} pattern
|
||||
* @returns {R}
|
||||
*
|
||||
* @throws MatchError
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef Variant
|
||||
* @property {cata<any>} cata
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {(...values: any[]) => Variant} VariantConstructor
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {PropertyKey[]} Variant
|
||||
* @typedef {{ [key in Variant[number]]: (...values: any) => Variant }} Variants
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {PropertyKey} T
|
||||
* @template {PropertyKey[]} U
|
||||
* @typedef {{ is: typeof is } & Variants<U>} Union
|
||||
*/
|
||||
|
||||
const Tag = Symbol('Tag')
|
||||
|
||||
export class CatamorphismError extends Error {
|
||||
/** @param {PropertyKey} name */
|
||||
constructor(name) {
|
||||
super(`unmatched arm in catamorphism: ${name.toString()}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {PropertyKey} type
|
||||
* @param {PropertyKey} variant
|
||||
* @returns {(...values: any[]) => Variant}
|
||||
*/
|
||||
const Variant = (type, variant) => (...values) => ({
|
||||
[Tag]: type,
|
||||
cata(pattern) {
|
||||
if (variant in pattern) {
|
||||
// NOTE: this is a workaround for typescript not recognizing
|
||||
// that objects can be indexed with symbols
|
||||
return pattern[ /** @type {string} */ (variant)](...values)
|
||||
} else if ('_' in pattern) {
|
||||
return pattern._()
|
||||
} else {
|
||||
throw new CatamorphismError(variant)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* @template T, U
|
||||
* @this {Union<T, U>}
|
||||
* @param {any} other
|
||||
* @returns {other is Variant<U>}
|
||||
*/
|
||||
function is(other) {
|
||||
return Object.hasOwn(other, Tag) && other[Tag] === this[Tag]
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {string} const T
|
||||
* @template {Array<PropertyKey>} const U
|
||||
* @param {T} typeName
|
||||
* @param {...U} variantNames
|
||||
* @returns {Union<T, U>}
|
||||
*/
|
||||
export const Union = (typeName, variantNames) => {
|
||||
const typeTag = Symbol(typeName)
|
||||
const tag = { [Tag]: typeTag, is }
|
||||
const variants = Object.fromEntries(variantNames.map(v => [v, Variant(typeTag, v)]))
|
||||
const result = Object.assign(tag, variants)
|
||||
|
||||
|
||||
return /** @type {Union<T, U>} */ (result)
|
||||
}
|
||||
|
22
src/utils.js
Normal file
22
src/utils.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
export const path = (path, obj) => {
|
||||
const parts = path.split('.')
|
||||
let current = obj
|
||||
let found = true
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
if (current && typeof current === 'object' && parts[i] in current) {
|
||||
current = current[parts[i]]
|
||||
} else {
|
||||
found = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return found
|
||||
}
|
||||
|
||||
export const zip = (...xs) => {
|
||||
const maxLength = Math.max(...xs.map(arr => arr.length))
|
||||
return Array.from({ length: maxLength }).map((_, i) =>
|
||||
xs.map(arr => arr[i])
|
||||
)
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { TerminalRunner } from 'folktest'
|
||||
import * as Tests from './units/index.js'
|
||||
|
||||
console.log(TerminalRunner(Tests).toString())
|
||||
|
228
tests/option.test.js
Normal file
228
tests/option.test.js
Normal file
|
@ -0,0 +1,228 @@
|
|||
import { Option, Some, None } from '../src/option.js'
|
||||
import { UnwrapError } from '../src/error.js'
|
||||
import { Result } from '../src/result.js'
|
||||
import { describe, it, mock } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
|
||||
const some = v => new Some(v)
|
||||
const eq = assert.deepStrictEqual.bind(assert)
|
||||
|
||||
describe('Option', () => {
|
||||
it('Option.some should return a Some', async () => {
|
||||
eq(Option.some(1), new Some(1))
|
||||
|
||||
// fantasy-land/of
|
||||
eq(Option['fantasy-land/of'](1), new Some(1))
|
||||
})
|
||||
|
||||
it('Some.bind(fn) should call fn', async () => {
|
||||
const a = some(1)
|
||||
let res = a.bind(x => some(x * 2))
|
||||
eq(res, some(2))
|
||||
|
||||
// fantasy-land/chain
|
||||
res = a['fantasy-land/chain'](x => some(x * 2))
|
||||
eq(res, some(2))
|
||||
})
|
||||
|
||||
it('None.bind(fn) should not execute fn', async () => {
|
||||
const fn = mock.fn(v => some(v * 2))
|
||||
let res = None.bind(fn)
|
||||
eq(res, None)
|
||||
eq(fn.mock.callCount(), 0)
|
||||
|
||||
// fantasy-land/chain
|
||||
res = None['fantasy-land/chain'](fn)
|
||||
eq(res, None)
|
||||
eq(fn.mock.callCount(), 0)
|
||||
})
|
||||
|
||||
it('Some.map(fn) should transform the inner value', () => {
|
||||
const a = some(1)
|
||||
let res = a.map(x => x + 1)
|
||||
eq(res, some(2))
|
||||
|
||||
// fantasy-land/map
|
||||
res = a['fantasy-land/map'](x => x + 1)
|
||||
eq(res, some(2))
|
||||
})
|
||||
|
||||
it('None.map(fn) should not execute fn', async () => {
|
||||
const fn = mock.fn(v => some(v * 2))
|
||||
let res = None.map(fn)
|
||||
eq(res, None)
|
||||
eq(fn.mock.callCount(), 0)
|
||||
|
||||
// fantasy-land/chain
|
||||
res = None['fantasy-land/map'](fn)
|
||||
eq(res, None)
|
||||
eq(fn.mock.callCount(), 0)
|
||||
})
|
||||
|
||||
it('Some.and(other)', () => {
|
||||
const a = some(1)
|
||||
const b = some(2)
|
||||
assert.strictEqual(a.and(b), b)
|
||||
})
|
||||
|
||||
it('None.and(other)', () => {
|
||||
assert.strictEqual(None.and(some(1)), None)
|
||||
})
|
||||
|
||||
it('Some.alt(other) should return itself', () => {
|
||||
const self = some(1)
|
||||
const other = some(2)
|
||||
let res = self.alt(other)
|
||||
assert.strictEqual(res, self)
|
||||
|
||||
// fantasy-land/alt
|
||||
res = self['fantasy-land/alt'](other)
|
||||
assert.strictEqual(res, self)
|
||||
})
|
||||
|
||||
it('None.alt(other) should return other', () => {
|
||||
const other = some(2)
|
||||
let res = None.alt(other)
|
||||
assert.strictEqual(res, other)
|
||||
|
||||
// fantasy-land/alt
|
||||
res = None['fantasy-land/alt'](other)
|
||||
assert.strictEqual(res, other)
|
||||
})
|
||||
|
||||
it('Option.orElse()', () => {
|
||||
const nothing = mock.fn(() => None)
|
||||
const chocolate = mock.fn(() => some('chocolate'))
|
||||
eq(some('vanilla').orElse(chocolate), some('vanilla'))
|
||||
eq(chocolate.mock.callCount(), 0)
|
||||
eq(None.orElse(chocolate), some('chocolate'))
|
||||
eq(chocolate.mock.callCount(), 1)
|
||||
eq(None.orElse(nothing), None)
|
||||
eq(nothing.mock.callCount(), 1)
|
||||
})
|
||||
|
||||
it('Some.filter(predicate)', () => {
|
||||
const a = some(1)
|
||||
assert.strictEqual(a.filter(x => x == 1), a)
|
||||
assert.strictEqual(a.filter(x => x == 2), None)
|
||||
|
||||
// fantasy-land/filter
|
||||
assert.strictEqual(a['fantasy-land/filter'](x => x == 1), a)
|
||||
})
|
||||
|
||||
it('None.filter(predicate)', () => {
|
||||
assert.strictEqual(None.filter(x => x == 1), None)
|
||||
|
||||
// fantasy-land/filter
|
||||
assert.strictEqual(None['fantasy-land/filter'](x => x == 1), None)
|
||||
})
|
||||
|
||||
it('Some.okOr(err)', () => {
|
||||
eq(some(1).okOr(2), Result.ok(1))
|
||||
})
|
||||
|
||||
it('None.okOr(err)', () => {
|
||||
eq(None.okOr(2), Result.err(2))
|
||||
})
|
||||
|
||||
it('Some.okOrElse(err)', () => {
|
||||
eq(some(1).okOrElse(() => 2), Result.ok(1))
|
||||
})
|
||||
|
||||
it('None.okOr(err)', () => {
|
||||
eq(None.okOrElse(() => 2), Result.err(2))
|
||||
})
|
||||
|
||||
it('Some.equals(other)', () => {
|
||||
const a = some(1)
|
||||
|
||||
assert.ok(a.equals(some(1)))
|
||||
assert(!a.equals(some(2)))
|
||||
assert(!a.equals(None))
|
||||
|
||||
// fantasy-land/equals
|
||||
assert.ok(a['fantasy-land/equals'](some(1)))
|
||||
})
|
||||
|
||||
it('None.equals(other)', () => {
|
||||
assert.ok(None.equals(None))
|
||||
assert(!None.equals(some(1)))
|
||||
|
||||
// fantasy-land/equals
|
||||
assert.ok(None['fantasy-land/equals'](None))
|
||||
})
|
||||
|
||||
it('Some.lte(other)', () => {
|
||||
const a = some(1)
|
||||
|
||||
assert.ok(a.lte(some(1)))
|
||||
assert.ok(a.lte(some(2)))
|
||||
assert.ok(!a.lte(some(0)))
|
||||
assert(!a.lte(None))
|
||||
|
||||
// fantasy-land/lte
|
||||
assert.ok(a['fantasy-land/lte'](some(1)))
|
||||
})
|
||||
|
||||
it('None.lte(other)', () => {
|
||||
assert(!None.lte(None))
|
||||
assert(!None.lte(some(1)))
|
||||
|
||||
// fantasy-land/lte
|
||||
assert.ok(!None['fantasy-land/lte'](None))
|
||||
})
|
||||
|
||||
it('Some.flatten()', async () => {
|
||||
eq(some(1).flatten(), some(1))
|
||||
eq(some(some(1)).flatten(), some(1))
|
||||
eq(some(some(some(1))).flatten(), some(some(1)))
|
||||
eq(some(None).flatten(), None)
|
||||
})
|
||||
|
||||
it('None.flatten()', async () => {
|
||||
eq(None.flatten(), None)
|
||||
})
|
||||
|
||||
it('Some.inspect(fn) should call fn', async () => {
|
||||
const fn = mock.fn(_x => { })
|
||||
some(1).inspect(fn)
|
||||
eq(fn.mock.callCount(), 1)
|
||||
})
|
||||
|
||||
it('None.inspect(fn) should not call fn', async () => {
|
||||
const fn = mock.fn(_x => { })
|
||||
None.inspect(fn)
|
||||
eq(fn.mock.callCount(), 0)
|
||||
})
|
||||
|
||||
it('Some.unwrap() returns inner value', async () => {
|
||||
const a = some(1)
|
||||
assert.doesNotThrow(a.unwrap.bind(a), UnwrapError)
|
||||
eq(a.unwrap(), 1)
|
||||
})
|
||||
|
||||
it('None.unwrap() throws UnwrapError', async () => {
|
||||
assert.throws(None.unwrap, UnwrapError)
|
||||
})
|
||||
|
||||
it('Some.unwrapOr(value) returns inner value', async () => {
|
||||
eq(some(1).unwrapOr(2), 1)
|
||||
})
|
||||
|
||||
it('None.unwrapOr(value) returns value', async () => {
|
||||
eq(None.unwrapOr(2), 2)
|
||||
})
|
||||
|
||||
it('Some.unwrapOrElse(fn) returns inner value', async () => {
|
||||
const fn = mock.fn(() => 2)
|
||||
const a = some(1)
|
||||
eq(a.unwrapOrElse(fn), 1)
|
||||
eq(fn.mock.callCount(), 0)
|
||||
})
|
||||
|
||||
it('None.unwrapOrElse(fn) returns result of fn', async () => {
|
||||
const fn = mock.fn(() => 2)
|
||||
eq(None.unwrapOrElse(fn), 2)
|
||||
eq(fn.mock.callCount(), 1)
|
||||
})
|
||||
})
|
276
tests/result.test.js
Normal file
276
tests/result.test.js
Normal file
|
@ -0,0 +1,276 @@
|
|||
import { Result, Ok, Err } from '../src/result.js'
|
||||
import { UnwrapError } from '../src/error.js'
|
||||
import { Option } from '../src/option.js'
|
||||
import { describe, it, mock } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
|
||||
const ok = v => new Ok(v)
|
||||
const err = e => new Err(e)
|
||||
const eq = assert.deepStrictEqual.bind(assert)
|
||||
|
||||
describe('Result', () => {
|
||||
it('Result.ok should return an Ok value', async () => {
|
||||
eq(Result.ok(1), new Ok(1))
|
||||
|
||||
// fantasy-land/of
|
||||
eq(Result['fantasy-land/of'](1), new Ok(1))
|
||||
})
|
||||
|
||||
it('Ok.bind(fn) should call fn', async () => {
|
||||
const a = ok(1)
|
||||
const fn = x => ok(x * 2)
|
||||
let res = a.bind(fn)
|
||||
eq(res, ok(2))
|
||||
|
||||
// fantasy-land/chain
|
||||
res = a['fantasy-land/chain'](fn)
|
||||
eq(res, ok(2))
|
||||
|
||||
// static-land chain
|
||||
res = Result.chain(fn, a)
|
||||
eq(res, ok(2))
|
||||
})
|
||||
|
||||
it('Err.bind(fn) should not execute fn', async () => {
|
||||
const fn = mock.fn(v => ok(v * 2))
|
||||
const e = err(new Error('failed'))
|
||||
let res = e.bind(fn)
|
||||
eq(res, err(new Error('failed')))
|
||||
eq(fn.mock.callCount(), 0)
|
||||
|
||||
// fantasy-land/chain
|
||||
res = e['fantasy-land/chain'](fn)
|
||||
eq(res, err(new Error('failed')))
|
||||
eq(fn.mock.callCount(), 0)
|
||||
|
||||
// static-land chain
|
||||
res = Result.chain(fn, e)
|
||||
eq(res, err(new Error('failed')))
|
||||
eq(fn.mock.callCount(), 0)
|
||||
})
|
||||
|
||||
it('Ok.map(fn) should transform the inner value', () => {
|
||||
const a = ok(1)
|
||||
const fn = x => x + 1
|
||||
let res = a.map(fn)
|
||||
eq(res, ok(2))
|
||||
|
||||
// fantasy-land/map
|
||||
res = a['fantasy-land/map'](fn)
|
||||
eq(res, ok(2))
|
||||
|
||||
// static-land map
|
||||
res = Result.map(fn, a)
|
||||
eq(res, ok(2))
|
||||
})
|
||||
|
||||
it('Err.map(fn) should not execute fn', async () => {
|
||||
const fn = mock.fn(v => Result.ok(v * 2))
|
||||
const e = err(new Error('failed'))
|
||||
let res = e.map(fn)
|
||||
eq(res, err(new Error('failed')))
|
||||
eq(fn.mock.callCount(), 0)
|
||||
|
||||
// fantasy-land/chain
|
||||
res = e['fantasy-land/map'](fn)
|
||||
eq(res, err(new Error('failed')))
|
||||
eq(fn.mock.callCount(), 0)
|
||||
|
||||
// static-land chain
|
||||
res = Result.map(fn, e)
|
||||
eq(res, err(new Error('failed')))
|
||||
eq(fn.mock.callCount(), 0)
|
||||
})
|
||||
|
||||
it('Ok.and(other)', () => {
|
||||
const a = ok(1)
|
||||
const b = ok(2)
|
||||
assert.strictEqual(a.and(b), b)
|
||||
})
|
||||
|
||||
it('Err.and(other)', () => {
|
||||
const a = err(1)
|
||||
const b = ok(2)
|
||||
assert.strictEqual(a.and(b), a)
|
||||
})
|
||||
|
||||
it('Ok.alt(other) should return itself', () => {
|
||||
const self = ok(1)
|
||||
const other = ok(2)
|
||||
let res = self.alt(other)
|
||||
assert.strictEqual(res, self)
|
||||
|
||||
// fantasy-land/alt
|
||||
res = self['fantasy-land/alt'](other)
|
||||
assert.strictEqual(res, self)
|
||||
|
||||
// static-land alt
|
||||
res = Result.alt(other, self)
|
||||
assert.strictEqual(res, self)
|
||||
})
|
||||
|
||||
it('Err.alt(other) should return other', () => {
|
||||
const self = err(new Error('failure'))
|
||||
const other = ok(2)
|
||||
let res = self.alt(other)
|
||||
assert.strictEqual(res, other)
|
||||
|
||||
// fantasy-land/alt
|
||||
res = self['fantasy-land/alt'](other)
|
||||
assert.strictEqual(res, other)
|
||||
|
||||
// static-land alt
|
||||
res = Result.alt(other, self)
|
||||
assert.strictEqual(res, other)
|
||||
})
|
||||
|
||||
it('Result.orElse(fn)', () => {
|
||||
const sq = x => ok(x * x)
|
||||
eq(ok(2).orElse(sq).orElse(sq), ok(2))
|
||||
eq(ok(2).orElse(err).orElse(sq), ok(2))
|
||||
eq(err(3).orElse(sq).orElse(err), ok(9))
|
||||
eq(err(3).orElse(err).orElse(err), err(3))
|
||||
})
|
||||
|
||||
it('Ok.ok()', () => {
|
||||
eq(ok(1).ok(), Option.some(1))
|
||||
})
|
||||
|
||||
it('Err.ok()', () => {
|
||||
eq(err(1).ok(), Option.none())
|
||||
})
|
||||
|
||||
it('Ok.err()', () => {
|
||||
eq(ok(1).err(), Option.none())
|
||||
})
|
||||
|
||||
it('Err.err()', () => {
|
||||
eq(err(1).err(), Option.some(1))
|
||||
})
|
||||
|
||||
it('Ok.equals(other)', () => {
|
||||
const a = ok(1)
|
||||
|
||||
assert.ok(a.equals(ok(1)))
|
||||
assert(!a.equals(ok(2)))
|
||||
assert(!a.equals(err(1)))
|
||||
|
||||
// fantasy-land/equals
|
||||
assert.ok(a['fantasy-land/equals'](ok(1)))
|
||||
|
||||
// static-land equals
|
||||
assert.ok(Result.equals(ok(1), a))
|
||||
})
|
||||
|
||||
it('Err.equals(other)', () => {
|
||||
const a = err(1)
|
||||
|
||||
assert.ok(a.equals(err(1)))
|
||||
assert(!a.equals(err(2)))
|
||||
assert(!a.equals(ok(1)))
|
||||
|
||||
// fantasy-land/equals
|
||||
assert.ok(a['fantasy-land/equals'](err(1)))
|
||||
|
||||
// static-land equals
|
||||
assert.ok(Result.equals(err(1), a))
|
||||
})
|
||||
|
||||
it('Ok.lte(other)', () => {
|
||||
const a = ok(1)
|
||||
|
||||
assert.ok(a.lte(ok(1)))
|
||||
assert.ok(a.lte(ok(2)))
|
||||
assert.ok(!a.lte(ok(0)))
|
||||
assert(!a.lte(err(1)))
|
||||
|
||||
// fantasy-land/lte
|
||||
assert.ok(a['fantasy-land/lte'](ok(1)))
|
||||
|
||||
// static-land lte
|
||||
assert.ok(Result.lte(ok(1), a))
|
||||
})
|
||||
|
||||
it('Err.lte(other)', () => {
|
||||
const a = err(1)
|
||||
|
||||
assert.ok(a.lte(err(1)))
|
||||
assert.ok(a.lte(err(2)))
|
||||
assert.ok(!a.lte(err(0)))
|
||||
assert(!a.lte(ok(1)))
|
||||
|
||||
// fantasy-land/lte
|
||||
assert.ok(a['fantasy-land/lte'](err(1)))
|
||||
|
||||
// static-land lte
|
||||
assert.ok(Result.lte(err(1), a))
|
||||
})
|
||||
|
||||
it('Ok.flatten()', async () => {
|
||||
eq(ok(1).flatten(), ok(1))
|
||||
eq(ok(ok(1)).flatten(), ok(1))
|
||||
eq(ok(ok(ok(1))).flatten(), ok(ok(1)))
|
||||
eq(ok(err(1)).flatten(), err(1))
|
||||
})
|
||||
|
||||
it('Err.flatten()', async () => {
|
||||
eq(err(1).flatten(), err(1))
|
||||
eq(err(err(1)).flatten(), err(1))
|
||||
})
|
||||
|
||||
it('Ok.inspect(fn) should call fn', async () => {
|
||||
const fn = mock.fn(_x => { })
|
||||
ok(1).inspect(fn)
|
||||
eq(fn.mock.callCount(), 1)
|
||||
})
|
||||
|
||||
it('Err.inspect(fn) should not call fn', async () => {
|
||||
const fn = mock.fn(_x => { })
|
||||
err(1).inspect(fn)
|
||||
eq(fn.mock.callCount(), 0)
|
||||
})
|
||||
|
||||
it('Ok.unwrap() returns inner value', async () => {
|
||||
const a = ok(1)
|
||||
assert.doesNotThrow(a.unwrap.bind(a), UnwrapError)
|
||||
eq(a.unwrap(), 1)
|
||||
})
|
||||
|
||||
it('Err.unwrap() throws UnwrapError', async () => {
|
||||
assert.throws(err(1).unwrap, UnwrapError)
|
||||
})
|
||||
|
||||
it('Ok.unwrapErr() throws UnwrapError', async () => {
|
||||
const a = ok(1)
|
||||
assert.throws(a.unwrapErr, UnwrapError)
|
||||
})
|
||||
|
||||
it('Err.unwrapErr() returns inner error', async () => {
|
||||
const a = err(1)
|
||||
assert.doesNotThrow(a.unwrapErr.bind(a), UnwrapError)
|
||||
eq(a.unwrapErr(), 1)
|
||||
})
|
||||
|
||||
it('Ok.unwrapOr(value) returns inner value', async () => {
|
||||
eq(ok(1).unwrapOr(2), 1)
|
||||
})
|
||||
|
||||
it('Err.unwrapOr(value) returns value', async () => {
|
||||
eq(err(1).unwrapOr(2), 2)
|
||||
})
|
||||
|
||||
it('Ok.unwrapOrElse(fn) returns inner value', async () => {
|
||||
const fn = mock.fn(() => 2)
|
||||
const a = ok(1)
|
||||
eq(a.unwrapOrElse(fn), 1)
|
||||
eq(fn.mock.callCount(), 0)
|
||||
})
|
||||
|
||||
it('Err.unwrapOrElse(fn) returns result of fn', async () => {
|
||||
const fn = mock.fn(() => 2)
|
||||
const a = err(1)
|
||||
eq(a.unwrapOrElse(fn), 2)
|
||||
eq(fn.mock.callCount(), 1)
|
||||
})
|
||||
})
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
export { Tests as Monad } from './monad.js'
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
// @ts-nocheck
|
||||
import { it, assert } from 'folktest'
|
||||
import { IO, List, Option, Reader, Result, Some } from '../../src/index.js'
|
||||
import { Applicative, Chain, Ord, Setoid } from '../../src/algebra/interfaces.js'
|
||||
|
||||
const Algebra = [Option, Result, List, IO, Reader]
|
||||
|
||||
const inc = F => x => F.of(x + 1)
|
||||
const dbl = F => x => F.of(x * 2)
|
||||
|
||||
const impl = i => m => i.implementedBy(m)
|
||||
|
||||
export const Tests = [
|
||||
it('unit is a left identity', () => {
|
||||
Algebra.filter(impl(Setoid, Applicative, Chain)).forEach(algebra => {
|
||||
const f = inc(algebra)
|
||||
|
||||
assert(
|
||||
algebra.of(1).chain(f).equals(f(1)),
|
||||
`${algebra.name} is not a left identity`
|
||||
)
|
||||
})
|
||||
}),
|
||||
|
||||
it('unit is a right identity', () => {
|
||||
Algebra.filter(impl(Setoid, Applicative, Chain)).forEach(algebra => {
|
||||
const m = algebra.of(1)
|
||||
|
||||
assert(
|
||||
m.chain(algebra.of).equals(m),
|
||||
`${algebra.name} is not a right identity`
|
||||
)
|
||||
})
|
||||
}),
|
||||
|
||||
it('unit is associative', () => {
|
||||
Algebra.filter(impl(Setoid, Applicative, Chain)).forEach(algebra => {
|
||||
const a = algebra.of(1)
|
||||
const f = inc(algebra)
|
||||
const g = dbl(algebra)
|
||||
|
||||
const x = a.chain(f).chain(g)
|
||||
const y = a.chain(x => f(x).chain(g))
|
||||
|
||||
assert(x.equals(y), `${algebra.name} is not associative`)
|
||||
})
|
||||
}),
|
||||
]
|
Loading…
Add table
Reference in a new issue