├── .github ├── renovate.json └── workflows │ ├── release-please.yaml │ └── test.yaml ├── .gitignore ├── .release-please-manifest.json ├── .releaserc.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── deno.json ├── deps.ts ├── mod.ts ├── release-please-config.json ├── scripts └── build_npm.ts ├── src ├── array.test.ts ├── array.ts ├── binary-heap.test.ts ├── binary-heap.ts ├── bool.test.ts ├── bool.ts ├── cat.test.ts ├── cat.ts ├── cofree.ts ├── cofree │ └── comonad.ts ├── compose.ts ├── const.ts ├── cont.test.ts ├── cont.ts ├── cont │ └── monad.ts ├── control-flow.test.ts ├── control-flow.ts ├── coyoneda.test.ts ├── coyoneda.ts ├── curry.ts ├── dom.ts ├── dual.ts ├── envelope.ts ├── ether.test.ts ├── ether.ts ├── exists.ts ├── free.test.ts ├── free.ts ├── free │ └── monad.ts ├── frozen.ts ├── func.test.ts ├── func.ts ├── graph.test.ts ├── graph.ts ├── hkt.ts ├── identity.ts ├── kleisli.ts ├── lazy.test.ts ├── lazy.ts ├── list.test.ts ├── list.ts ├── map.test.ts ├── map.ts ├── matrix.test.ts ├── matrix.ts ├── multi-set.test.ts ├── multi-set.ts ├── mut.test.ts ├── mut.ts ├── number.test.ts ├── number.ts ├── optical.ts ├── optical │ ├── getter.ts │ ├── lens.test.ts │ ├── lens.ts │ ├── parallel.ts │ ├── prism.test.ts │ ├── prism.ts │ ├── retriable.test.ts │ ├── retriable.ts │ ├── sequential.ts │ ├── setter.test.ts │ ├── setter.ts │ ├── traversal.test.ts │ └── traversal.ts ├── option.test.ts ├── option.ts ├── ordering.ts ├── predicate.ts ├── promise.test.ts ├── promise.ts ├── promise │ └── monad.ts ├── range-q.test.ts ├── range-q.ts ├── reader.test.ts ├── reader.ts ├── reader │ └── monad.ts ├── record.test.ts ├── record.ts ├── result.test.ts ├── result.ts ├── reverse.test.ts ├── reverse.ts ├── seg-tree.test.ts ├── seg-tree.ts ├── seq.ts ├── seq │ ├── finger-tree.test.ts │ └── finger-tree.ts ├── serial.test.ts ├── serial.ts ├── star.ts ├── state.test.ts ├── state.ts ├── state │ └── monad.ts ├── store.test.ts ├── store.ts ├── store │ └── comonad.ts ├── string.ts ├── tagged.ts ├── these.test.ts ├── these.ts ├── trans.ts ├── tropical.test.ts ├── tropical.ts ├── tuple-n.ts ├── tuple.test.ts ├── tuple.ts ├── type-class.ts ├── type-class │ ├── abelian-group.ts │ ├── abelian-monoid.ts │ ├── applicative.ts │ ├── apply.test.ts │ ├── apply.ts │ ├── arrow.ts │ ├── associative.ts │ ├── bifoldable.ts │ ├── bifunctor.ts │ ├── bitraversable.ts │ ├── category.ts │ ├── choice.ts │ ├── comonad.ts │ ├── conjoined.ts │ ├── corepresentable.ts │ ├── distributive.ts │ ├── endo.ts │ ├── eq.ts │ ├── error-monad.ts │ ├── field.ts │ ├── flat-map.ts │ ├── foldable.ts │ ├── functor.ts │ ├── group.test.ts │ ├── group.ts │ ├── has-inf.ts │ ├── has-neg-inf.ts │ ├── hash.ts │ ├── indexable.ts │ ├── indexed.ts │ ├── iso.ts │ ├── magma.ts │ ├── monad-rec.ts │ ├── monad.ts │ ├── monoid.test.ts │ ├── monoid.ts │ ├── monoidal.ts │ ├── nt.ts │ ├── ord.ts │ ├── partial-eq.ts │ ├── partial-ord.ts │ ├── profunctor.ts │ ├── pure.ts │ ├── reduce.ts │ ├── representable.ts │ ├── reviewable.ts │ ├── ring.ts │ ├── semi-group.ts │ ├── semi-groupal.ts │ ├── semi-groupoid.ts │ ├── semi-ring.ts │ ├── settable.ts │ ├── strong.ts │ ├── symmetric.ts │ ├── tensor.ts │ ├── traversable-monad.ts │ ├── traversable.ts │ ├── unital.ts │ └── variance.ts ├── writer.test.ts ├── writer.ts ├── writer │ └── monad.ts ├── yoneda.ts ├── zipper.test.ts └── zipper.ts ├── tsconfig.doc.json └── tsconfig.json /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:base", ":timezone(Asia/Tokyo)"], 4 | "semanticCommits": "enabled", 5 | "fetchReleaseNotes": true, 6 | "enabledManagers": ["github-actions"], 7 | "schedule": ["on the first day of the week"], 8 | "commitMessagePrefix": "chore(deps): ", 9 | "dependencyDashboard": true, 10 | "prHourlyLimit": 0, 11 | "platformAutomerge": true, 12 | "prConcurrentLimit": 5, 13 | "automerge": true, 14 | "major": { 15 | "automerge": false 16 | }, 17 | "vulnerabilityAlerts": { 18 | "enabled": true, 19 | "automerge": true 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: 3 | inputs: 4 | force: 5 | description: Whether forcing to publish a release 6 | required: true 7 | type: boolean 8 | release_version: 9 | description: Version to publish (vX.Y.Z) 10 | required: true 11 | type: string 12 | push: 13 | branches: 14 | - main 15 | 16 | permissions: 17 | contents: write 18 | id-token: write 19 | pull-requests: write 20 | 21 | name: release-please 22 | jobs: 23 | release-please: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: googleapis/release-please-action@v4 27 | id: release_please 28 | with: 29 | config-file: release-please-config.json 30 | manifest-file: .release-please-manifest.json 31 | outputs: 32 | release_created: ${{ steps.release_please.outputs.release_created }} 33 | tag_name: ${{ steps.release_please.outputs.tag_name }} 34 | 35 | publish: 36 | runs-on: ubuntu-latest 37 | needs: release-please 38 | if: ${{ needs.release-please.outputs.release_created || github.event.inputs.force }} 39 | env: 40 | VERSION: ${{ needs.release-please.outputs.tag_name || inputs.release_version }} 41 | steps: 42 | - uses: actions/checkout@v4 43 | with: 44 | fetch-depth: 0 45 | - uses: denoland/setup-deno@v1 46 | with: 47 | deno-version: v1.x 48 | 49 | - name: Generate docs 50 | run: deno task doc 51 | 52 | - name: Deploy to GitHub Pages 53 | uses: peaceiris/actions-gh-pages@v4 54 | with: 55 | github_token: ${{ secrets.GITHUB_TOKEN }} 56 | publish_dir: ./docs 57 | 58 | - name: Setup Node.js package 59 | run: | 60 | deno run -A ./scripts/build_npm.ts ${VERSION#v} 61 | 62 | - uses: JS-DevTools/npm-publish@v3 63 | with: 64 | token: ${{ secrets.NPM_TOKEN }} 65 | package: npm/ 66 | provenance: true 67 | 68 | - name: Publish package 69 | run: npx jsr publish 70 | 71 | - name: Upload files 72 | env: 73 | GH_TOKEN: ${{ github.token }} 74 | run: | 75 | set -e 76 | echo "Creating tarball" 77 | tar cvzf raw.tar.gz --directory=npm/ . 78 | echo "Creating zip" 79 | pushd npm 80 | zip -r "$GITHUB_WORKSPACE/raw.zip" ./* 81 | popd 82 | echo "Uploading release assets" 83 | gh release upload ${{ env.VERSION }} raw.zip raw.tar.gz --clobber 84 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | push: 7 | branches: [main] 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: denoland/setup-deno@v1 15 | with: 16 | deno-version: v1.x 17 | - name: Check format 18 | run: | 19 | deno fmt --check 20 | deno lint 21 | - name: Generate coverage 22 | run: | 23 | deno task coverage 24 | deno coverage --lcov --output=coverage/cov.lcov coverage/cov_profile/ 25 | - uses: codecov/codecov-action@v5 26 | with: 27 | files: ./coverage/cov.lcov 28 | name: mini-fn 29 | verbose: true 30 | fail_ci_if_error: false 31 | token: ${{ secrets.CODECOV_TOKEN }} 32 | 33 | - name: Upload test results to Codecov 34 | if: ${{ !cancelled() }} 35 | uses: codecov/test-results-action@v1 36 | with: 37 | token: ${{ secrets.CODECOV_TOKEN }} 38 | 39 | - name: Dry run publish 40 | run: deno publish --dry-run 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # codecov 29 | codecov 30 | codecov.* 31 | 32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | node_modules/ 46 | jspm_packages/ 47 | 48 | # TypeScript v1 declaration files 49 | typings/ 50 | 51 | # TypeScript cache 52 | *.tsbuildinfo 53 | 54 | # Optional npm cache directory 55 | .npm 56 | 57 | # Optional eslint cache 58 | .eslintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variables file 76 | .env 77 | .env.test 78 | 79 | # parcel-bundler cache (https://parceljs.org/) 80 | .cache 81 | 82 | # Next.js build output 83 | .next 84 | 85 | # Nuxt.js build / generate output 86 | .nuxt 87 | dist 88 | 89 | # Gatsby files 90 | .cache/ 91 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 92 | # https://nextjs.org/blog/next-9-1#public-directory-support 93 | # public 94 | 95 | # vuepress build output 96 | .vuepress/dist 97 | 98 | # Serverless directories 99 | .serverless/ 100 | 101 | # FuseBox cache 102 | .fusebox/ 103 | 104 | # DynamoDB Local files 105 | .dynamodb/ 106 | 107 | # TernJS port file 108 | .tern-port 109 | 110 | # Yarn PnP 111 | .pnp.* 112 | .yarn/* 113 | !.yarn/patches 114 | !.yarn/plugins 115 | !.yarn/releases 116 | !.yarn/sdks 117 | !.yarn/versions 118 | 119 | # Generated docs 120 | docs/ 121 | 122 | # npm build 123 | npm/ 124 | 125 | # Test Analytics 126 | junit.xml 127 | -------------------------------------------------------------------------------- /.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | ".": "7.0.0" 3 | } -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "@semantic-release/commit-analyzer", 4 | "@semantic-release/release-notes-generator", 5 | "@semantic-release/npm", 6 | "@semantic-release/github" 7 | ], 8 | "branches": [ 9 | "main" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mini-fn 2 | 3 | The minimal functional programming library. 4 | 5 | [![codecov](https://codecov.io/github/MikuroXina/mini-fn/branch/main/graph/badge.svg?token=3HZ2Y5T1A2)](https://codecov.io/github/MikuroXina/mini-fn) 6 | 7 | --- 8 | 9 | mini-fn provides simple, tiny library having functional features and its type 10 | declarations. 11 | 12 | ## Examples 13 | 14 | You can pipe your functions with `Cat`'s `feed(f: (t: T) => U): Cat` 15 | method like this: 16 | 17 | ```ts 18 | import { Cat } from "@mikuroxina/mini-fn"; 19 | 20 | const result = Cat.cat(-3) 21 | .feed((x) => x ** 2) 22 | .feed((x) => x.toString()); 23 | console.log(result.value); // "9" 24 | ``` 25 | 26 | And there are some useful types such as `Option`, `Result`, and so on. 27 | 28 | ```ts 29 | import { Option } from "@mikuroxina/mini-fn"; 30 | const sqrtThenToString = (num: number): Option.Option => { 31 | if (num < 0) { 32 | return Option.none(); 33 | } 34 | return Option.some(Math.sqrt(num).toString()); 35 | }; 36 | 37 | const applied = Option.andThen(sqrtThenToString); 38 | applied(Option.some(4)); // some("2") 39 | applied(Option.some(-1)); // none 40 | applied(Option.none()); // none 41 | ``` 42 | 43 | Some of them also provides its monad implementation, so you can combine and 44 | transform them like this: 45 | 46 | ```ts 47 | import { Cat, Option } from "@mikuroxina/mini-fn"; 48 | 49 | const half = (x: number): Option.Option => { 50 | if (x % 2 != 0) { 51 | return Option.none(); 52 | } 53 | return Option.some(x / 2); 54 | }; 55 | const liftedHalf = Option.monad.flatMap(half); 56 | 57 | Cat.cat(20) 58 | .feed(Option.monad.pure) 59 | .feed(liftedHalf) 60 | .feed(Cat.log) // some(10) 61 | .feed(liftedHalf) 62 | .feed(Cat.log) // some(5) 63 | .feed(liftedHalf) 64 | .feed(Cat.log); // none 65 | ``` 66 | 67 | Also `CatT` allows you to compute with a `Monad` environment as: 68 | 69 | ```ts 70 | import { Cat, List } from "@mikuroxina/mini-fn"; 71 | 72 | // Find patterns where `x + y + z == 5` for all natural number `x`, `y`, and `z`. 73 | const patterns = Cat.doT(List.monad) 74 | .addM("x", List.range(0, 6)) 75 | .addMWith("y", ({ x }) => List.range(0, 6 - x)) 76 | .addWith("z", ({ x, y }) => 5 - (x + y)) 77 | .finish(({ x, y, z }) => [x, y, z] as const); 78 | 79 | console.dir(List.toArray(patterns)); 80 | /* [ 81 | [0, 0, 5], 82 | [0, 1, 4], 83 | [0, 2, 3], 84 | [0, 3, 2], 85 | [0, 4, 1], 86 | [0, 5, 0], 87 | [1, 0, 4], 88 | [1, 1, 3], 89 | [1, 2, 2], 90 | [1, 3, 1], 91 | [1, 4, 0], 92 | [2, 0, 3], 93 | [2, 1, 2], 94 | [2, 2, 1], 95 | [2, 3, 0], 96 | [3, 0, 2], 97 | [3, 1, 1], 98 | [3, 2, 0], 99 | [4, 0, 1], 100 | [4, 1, 0], 101 | [5, 0, 0], 102 | ] */ 103 | ``` 104 | -------------------------------------------------------------------------------- /deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": { 3 | "dev": "deno run --watch mod.ts", 4 | "test": "deno test -A src/ && deno test -A --doc src/", 5 | "coverage": "deno test -A --doc --reporter=junit --junit-path=./junit.xml --coverage=./coverage/cov_profile src/", 6 | "doc": "deno run --allow-read --allow-write --allow-env --deny-run npm:typedoc@0.25.3 --tsconfig tsconfig.doc.json --skipErrorChecking --name @mikuroxina/mini-fn mod.ts" 7 | }, 8 | "fmt": { 9 | "useTabs": false, 10 | "lineWidth": 80, 11 | "indentWidth": 4, 12 | "semiColons": true, 13 | "singleQuote": false, 14 | "proseWrap": "always", 15 | "exclude": [ 16 | ".release-please-manifest.json", 17 | "CHANGELOG.md" 18 | ] 19 | }, 20 | "lint": { 21 | "include": [ 22 | "src/" 23 | ], 24 | "rules": { 25 | "tags": [ 26 | "recommended" 27 | ], 28 | "include": [ 29 | "camelcase", 30 | "default-param-last", 31 | "eqeqeq", 32 | "explicit-function-return-type", 33 | "guard-for-in", 34 | "no-const-assign", 35 | "no-eval", 36 | "no-external-import", 37 | "no-self-compare", 38 | "no-throw-literal", 39 | "no-top-level-await", 40 | "no-undef", 41 | "prefer-ascii", 42 | "verbatim-module-syntax" 43 | ] 44 | } 45 | }, 46 | "lock": false, 47 | "imports": { 48 | "@deno/dnt": "jsr:@deno/dnt@^0.41.1" 49 | }, 50 | "name": "@mikuroxina/mini-fn", 51 | "exports": "./mod.ts", 52 | "version": "7.0.0" 53 | } 54 | -------------------------------------------------------------------------------- /deps.ts: -------------------------------------------------------------------------------- 1 | export { 2 | assertEquals, 3 | assertThrows, 4 | } from "https://deno.land/std@0.206.0/assert/mod.ts"; 5 | export { 6 | assertSpyCall, 7 | spy, 8 | } from "https://deno.land/std@0.206.0/testing/mock.ts"; 9 | -------------------------------------------------------------------------------- /mod.ts: -------------------------------------------------------------------------------- 1 | export * as Array from "./src/array.ts"; 2 | export * as BinaryHeap from "./src/binary-heap.ts"; 3 | export * as Bool from "./src/bool.ts"; 4 | export * as Cat from "./src/cat.ts"; 5 | export * as Cofree from "./src/cofree.ts"; 6 | export * as ComonadCofree from "./src/cofree/comonad.ts"; 7 | export * as Compose from "./src/compose.ts"; 8 | export * as Const from "./src/const.ts"; 9 | export * as Cont from "./src/cont.ts"; 10 | export * as MonadCont from "./src/cont/monad.ts"; 11 | export * as ControlFlow from "./src/control-flow.ts"; 12 | export * as Coyoneda from "./src/coyoneda.ts"; 13 | export * as Curry from "./src/curry.ts"; 14 | export * as Dom from "./src/dom.ts"; 15 | export * as Dual from "./src/dual.ts"; 16 | export * as Envelope from "./src/envelope.ts"; 17 | export * as Ether from "./src/ether.ts"; 18 | export * as Exists from "./src/exists.ts"; 19 | export * as Free from "./src/free.ts"; 20 | export * as MonadFree from "./src/free/monad.ts"; 21 | export * as Frozen from "./src/frozen.ts"; 22 | export * as Func from "./src/func.ts"; 23 | export * as Graph from "./src/graph.ts"; 24 | export * as Hkt from "./src/hkt.ts"; 25 | export * as Identity from "./src/identity.ts"; 26 | export * as Kleisli from "./src/kleisli.ts"; 27 | export * as Lazy from "./src/lazy.ts"; 28 | export * as List from "./src/list.ts"; 29 | export * as Map from "./src/map.ts"; 30 | export * as Matrix from "./src/matrix.ts"; 31 | export * as MultiSet from "./src/multi-set.ts"; 32 | export * as Number from "./src/number.ts"; 33 | export * as Optical from "./src/optical.ts"; 34 | export * as Option from "./src/option.ts"; 35 | export * as Ordering from "./src/ordering.ts"; 36 | export * as Predicate from "./src/predicate.ts"; 37 | export * as Promise from "./src/promise.ts"; 38 | export * as MonadPromise from "./src/promise/monad.ts"; 39 | export * as RangeQ from "./src/range-q.ts"; 40 | export * as Reader from "./src/reader.ts"; 41 | export * as MonadReader from "./src/reader/monad.ts"; 42 | export * as Record from "./src/record.ts"; 43 | export * as Result from "./src/result.ts"; 44 | export * as Reverse from "./src/reverse.ts"; 45 | export * as SegTree from "./src/seg-tree.ts"; 46 | export * as Serial from "./src/serial.ts"; 47 | export * as Seq from "./src/seq.ts"; 48 | export * as FingerTree from "./src/seq/finger-tree.ts"; 49 | export * as Mut from "./src/mut.ts"; 50 | export * as Star from "./src/star.ts"; 51 | export * as State from "./src/state.ts"; 52 | export * as StateMonad from "./src/state/monad.ts"; 53 | export * as MonadWriter from "./src/state/monad.ts"; 54 | export * as Store from "./src/store.ts"; 55 | export * as ComonadStore from "./src/store/comonad.ts"; 56 | export * as String from "./src/string.ts"; 57 | export * as Tagged from "./src/tagged.ts"; 58 | export * as These from "./src/these.ts"; 59 | export * as Trans from "./src/trans.ts"; 60 | export * as Tropical from "./src/tropical.ts"; 61 | export * as Tuple from "./src/tuple.ts"; 62 | export * as TupleN from "./src/tuple-n.ts"; 63 | export * as TypeClass from "./src/type-class.ts"; 64 | export * as Writer from "./src/writer.ts"; 65 | export * as Yoneda from "./src/yoneda.ts"; 66 | export * as Zipper from "./src/zipper.ts"; 67 | -------------------------------------------------------------------------------- /release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://github.com/googleapis/release-please/raw/main/schemas/config.json", 3 | "packages": { 4 | ".": { 5 | "release-type": "simple", 6 | "changelog-path": "CHANGELOG.md", 7 | "extra-files": [ 8 | { 9 | "type": "json", 10 | "path": "deno.json", 11 | "jsonpath": "$.version" 12 | } 13 | ] 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /scripts/build_npm.ts: -------------------------------------------------------------------------------- 1 | import { build, emptyDir } from "@deno/dnt"; 2 | 3 | await emptyDir("./npm"); 4 | 5 | await build({ 6 | entryPoints: ["./mod.ts"], 7 | outDir: "./npm", 8 | shims: { 9 | deno: true, 10 | }, 11 | typeCheck: false, 12 | test: false, 13 | package: { 14 | name: "@mikuroxina/mini-fn", 15 | version: Deno.args[0], 16 | description: "The minimal functional programming library.", 17 | license: "Apache-2.0", 18 | repository: { 19 | type: "git", 20 | url: "git+https://github.com/MikuroXina/mini-fn.git", 21 | }, 22 | homepage: "https://mikuroxina.github.io/mini-fn/", 23 | bugs: { 24 | url: "https://github.com/MikuroXina/mini-fn/issues", 25 | }, 26 | sideEffects: false, 27 | }, 28 | postBuild() { 29 | Deno.copyFileSync("LICENSE", "npm/LICENSE"); 30 | Deno.copyFileSync("README.md", "npm/README.md"); 31 | }, 32 | }); 33 | -------------------------------------------------------------------------------- /src/bool.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "../deps.ts"; 2 | import { andMonoid, dec, enc, equality, orMonoid } from "./bool.ts"; 3 | import { unwrap } from "./result.ts"; 4 | import { runCode, runDecoder } from "./serial.ts"; 5 | 6 | const patterns = [false, true]; 7 | const patterns2 = patterns.flatMap((x) => patterns.map((y) => [x, y])); 8 | const patterns3 = patterns.flatMap((x) => patterns2.map((ys) => [x, ...ys])); 9 | 10 | Deno.test("logical and monoid", () => { 11 | // associative 12 | for (const [x, y, z] of patterns3) { 13 | assertEquals( 14 | andMonoid.combine(andMonoid.combine(x, y), z), 15 | andMonoid.combine(x, andMonoid.combine(y, z)), 16 | ); 17 | } 18 | 19 | // identity 20 | for (const x of patterns) { 21 | assertEquals(andMonoid.combine(andMonoid.identity, x), x); 22 | assertEquals(andMonoid.combine(x, andMonoid.identity), x); 23 | } 24 | }); 25 | 26 | Deno.test("logical or monoid", () => { 27 | // associative 28 | for (const [x, y, z] of patterns3) { 29 | assertEquals( 30 | orMonoid.combine(orMonoid.combine(x, y), z), 31 | orMonoid.combine(x, orMonoid.combine(y, z)), 32 | ); 33 | } 34 | 35 | // identity 36 | for (const x of patterns) { 37 | assertEquals(orMonoid.combine(orMonoid.identity, x), x); 38 | assertEquals(orMonoid.combine(x, orMonoid.identity), x); 39 | } 40 | }); 41 | 42 | Deno.test("equality", () => { 43 | for (const [x, y] of patterns2) { 44 | assertEquals(equality(x, y), x === y); 45 | } 46 | }); 47 | 48 | Deno.test("encode then decode", () => { 49 | for (const x of patterns) { 50 | const code = runCode(enc()(x)); 51 | const decoded = unwrap(runDecoder(dec())(code)); 52 | assertEquals(decoded, x); 53 | } 54 | }); 55 | -------------------------------------------------------------------------------- /src/bool.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type Decoder, 3 | decU8, 4 | type Encoder, 5 | encU8, 6 | mapDecoder, 7 | } from "./serial.ts"; 8 | import { type Eq, fromEquality } from "./type-class/eq.ts"; 9 | import type { Monoid } from "./type-class/monoid.ts"; 10 | import { semiGroupSymbol } from "./type-class/semi-group.ts"; 11 | 12 | /** 13 | * The instance of `Monoid` about logical AND operation. 14 | */ 15 | export const andMonoid: Monoid = { 16 | identity: true, 17 | combine: (l, r) => l && r, 18 | [semiGroupSymbol]: true, 19 | }; 20 | /** 21 | * The instance of `Monoid` about logical OR operation. 22 | */ 23 | export const orMonoid: Monoid = { 24 | identity: false, 25 | combine: (l, r) => l || r, 26 | [semiGroupSymbol]: true, 27 | }; 28 | 29 | export const equality = (lhs: boolean, rhs: boolean): boolean => lhs === rhs; 30 | export const eq: Eq = fromEquality(() => equality)(); 31 | 32 | export const enc = (): Encoder => (value) => encU8(value ? 1 : 0); 33 | export const dec = (): Decoder => mapDecoder((v) => v !== 0)(decU8()); 34 | -------------------------------------------------------------------------------- /src/cat.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals, spy } from "../deps.ts"; 2 | import { 3 | apply, 4 | cat, 5 | doT, 6 | flatMap, 7 | flatten, 8 | get, 9 | inspect, 10 | map, 11 | product, 12 | withT, 13 | } from "./cat.ts"; 14 | import { newBreak, newContinue } from "./control-flow.ts"; 15 | import { get as getState, monad, put, runState } from "./state.ts"; 16 | 17 | const m = monad(); 18 | 19 | Deno.test("when", () => { 20 | const comp = doT(m) 21 | .when(() => false, () => put(2)) 22 | .addM("x", getState()) 23 | .runWith(({ x }) => { 24 | assertEquals(x, 0); 25 | return m.pure([]); 26 | }) 27 | .when(() => true, () => put(3)) 28 | .addM("x", getState()) 29 | .runWith(({ x }) => { 30 | assertEquals(x, 3); 31 | return m.pure([]); 32 | }) 33 | .when(() => false, () => put(4)) 34 | .addM("x", getState()) 35 | .runWith(({ x }) => { 36 | assertEquals(x, 3); 37 | return m.pure([]); 38 | }).finish(() => []); 39 | const res = runState(comp)(0); 40 | assertEquals(res, [[], 3]); 41 | }); 42 | 43 | Deno.test("loop", () => { 44 | const mock = spy((_x: string) => []); 45 | runState( 46 | doT(m).loop( 47 | "", 48 | (state) => { 49 | mock(state); 50 | return m.pure( 51 | state === "xxxxx" ? newBreak([]) : newContinue(state + "x"), 52 | ); 53 | }, 54 | ).finish(() => []), 55 | )(0); 56 | assertEquals(mock.calls.flatMap(({ args }) => args), [ 57 | "", 58 | "x", 59 | "xx", 60 | "xxx", 61 | "xxxx", 62 | "xxxxx", 63 | ]); 64 | }); 65 | 66 | Deno.test("while", () => { 67 | const mock = spy((_x: number) => []); 68 | runState( 69 | doT(m).while( 70 | () => m.map((x: number) => x > 0)(getState()), 71 | () => 72 | doT(m).addM("x", getState()) 73 | .runWith(({ x }) => { 74 | mock(x); 75 | return put(x - 1); 76 | }) 77 | .finish(() => []), 78 | ).finish(() => []), 79 | )(3); 80 | assertEquals(mock.calls.flatMap(({ args }) => args), [ 81 | 3, 82 | 2, 83 | 1, 84 | ]); 85 | }); 86 | 87 | Deno.test("withT", () => { 88 | const res = runState(withT(m)({ x: 3 }).finish(({ x }) => x + 1))(0); 89 | assertEquals(res, [4, 0]); 90 | }); 91 | 92 | Deno.test("get", () => { 93 | const contained = cat("foo"); 94 | assertEquals(get(contained), "foo"); 95 | }); 96 | 97 | Deno.test("inspect", () => { 98 | const mock = spy((_x: string) => []); 99 | 100 | const res = cat("foo") 101 | .feed(inspect(mock)).value; 102 | 103 | assertEquals(res, "foo"); 104 | assertEquals(mock.calls[0].args, ["foo"]); 105 | }); 106 | 107 | Deno.test("flatten", () => { 108 | assertEquals(flatten(cat(cat("bar"))).value, "bar"); 109 | }); 110 | 111 | Deno.test("product", () => { 112 | assertEquals(product(cat("baz"))(cat(8)).value, ["baz", 8]); 113 | }); 114 | 115 | Deno.test("map", () => { 116 | assertEquals(map((x: number) => 2 * x)(cat(3)).value, 6); 117 | }); 118 | 119 | Deno.test("flatMap", () => { 120 | assertEquals(flatMap((x: number) => cat(3 + x))(cat(39)).value, 42); 121 | }); 122 | 123 | Deno.test("apply", () => { 124 | assertEquals(apply(cat((x: number) => x / 2))(cat(8)).value, 4); 125 | }); 126 | -------------------------------------------------------------------------------- /src/cofree/comonad.ts: -------------------------------------------------------------------------------- 1 | import type { Get1 } from "../hkt.ts"; 2 | import type { Comonad } from "../type-class/comonad.ts"; 3 | import type { Functor } from "../type-class/functor.ts"; 4 | 5 | export type ComonadCofree = Comonad & { 6 | functor: Functor; 7 | readonly unwrap: (wa: Get1) => Get1>; 8 | }; 9 | -------------------------------------------------------------------------------- /src/compose.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This module provides a composing functor, `Compose`. 3 | * 4 | * @packageDocumentation 5 | * @module 6 | */ 7 | 8 | import type { Apply2Only, Apply3Only, Get1, Hkt3 } from "./hkt.ts"; 9 | import { type Applicative, liftA2 } from "./type-class/applicative.ts"; 10 | import { collect, type Distributive } from "./type-class/distributive.ts"; 11 | import type { Foldable } from "./type-class/foldable.ts"; 12 | import type { Functor } from "./type-class/functor.ts"; 13 | import type { PartialEqUnary } from "./type-class/partial-eq.ts"; 14 | import type { Traversable } from "./type-class/traversable.ts"; 15 | 16 | /** 17 | * Right to left composition of `F` and `G` functors. 18 | */ 19 | export type Compose = Get1>; 20 | 21 | export interface ComposeHkt extends Hkt3 { 22 | readonly type: Compose; 23 | } 24 | 25 | export const partialEqUnary = ( 26 | eqUnaryF: PartialEqUnary, 27 | eqUnaryG: PartialEqUnary, 28 | ): PartialEqUnary, G>> => ({ 29 | liftEq: (equality) => eqUnaryF.liftEq(eqUnaryG.liftEq(equality)), 30 | }); 31 | 32 | export const newCompose = ( 33 | fgt: Get1>, 34 | ): Compose => fgt; 35 | export const getCompose = (c: Compose): Get1> => 36 | c; 37 | 38 | export const map = 39 | (f: Functor, g: Functor) => 40 | (fn: (t: T) => U): (t: Compose) => Compose => 41 | f.map(g.map(fn)); 42 | 43 | /** 44 | * Composes the two functors into a new one. 45 | * 46 | * @param f - The `Functor` instance for `F`. 47 | * @param g - The `Functor` instance for `G`. 48 | * @returns The composed functor. 49 | */ 50 | export const functor = 51 | (f: Functor) => 52 | (g: Functor): Functor, G>> => ({ 53 | map: map(f, g), 54 | }); 55 | 56 | /** 57 | * Composes the two applicative functors into a new one. 58 | * 59 | * @param f - The `Applicative` instance for `F`. 60 | * @param g - The `Applicative` instance for `G`. 61 | * @returns The composed applicative functor. 62 | */ 63 | export const applicative = (f: Applicative) => 64 | ( 65 | g: Applicative, 66 | ): Applicative, G>> => ({ 67 | pure: (t) => f.pure(g.pure(t)), 68 | map: map(f, g), 69 | apply: liftA2(f)(g.apply), 70 | }); 71 | 72 | /** 73 | * Composes the two composing operators into a new one. 74 | * 75 | * @param f - The `Foldable` instance for `F`. 76 | * @param g - The `Foldable` instance for `G`. 77 | * @returns The composed folding operator. 78 | */ 79 | export const foldable = (f: Foldable) => 80 | ( 81 | g: Foldable, 82 | ): Foldable, G>> => ({ 83 | foldR: (folder: (next: A) => (acc: B) => B) => 84 | f.foldR((ga: Get1) => (acc: B) => g.foldR(folder)(acc)(ga)), 85 | }); 86 | 87 | /** 88 | * Composes the two traversable functors into a new one. 89 | * 90 | * @param f - The `Traversable` instance for `F`. 91 | * @param g - The `Traversable` instance for `G`. 92 | * @returns The composed traversable functor. 93 | */ 94 | export const traversable = (f: Traversable) => 95 | ( 96 | g: Traversable, 97 | ): Traversable, G>> => ({ 98 | map: map(f, g), 99 | foldR: (folder: (next: A) => (acc: B) => B) => 100 | f.foldR((ga: Get1) => (acc: B) => g.foldR(folder)(acc)(ga)), 101 | traverse: (app) => (visitor) => f.traverse(app)(g.traverse(app)(visitor)), 102 | }); 103 | 104 | export const distributive = ( 105 | distributiveF: Distributive, 106 | distributiveG: Distributive, 107 | ): Distributive, G>> => ({ 108 | map: map(distributiveF, distributiveG), 109 | distribute: 110 | (functor: Functor) => 111 | (fga: Get1>): Compose> => 112 | distributiveF.map(distributiveG.distribute(functor))( 113 | collect(distributiveF)(functor)(getCompose)(fga), 114 | ), 115 | }); 116 | -------------------------------------------------------------------------------- /src/const.ts: -------------------------------------------------------------------------------- 1 | import type { Apply2Only, Hkt2 } from "./hkt.ts"; 2 | import type { Applicative } from "./type-class/applicative.ts"; 3 | import type { Bifunctor } from "./type-class/bifunctor.ts"; 4 | import { type Eq, eqSymbol } from "./type-class/eq.ts"; 5 | import type { Foldable } from "./type-class/foldable.ts"; 6 | import type { Functor } from "./type-class/functor.ts"; 7 | import type { Monoid } from "./type-class/monoid.ts"; 8 | import type { Ord } from "./type-class/ord.ts"; 9 | import type { PartialEq, PartialEqUnary } from "./type-class/partial-eq.ts"; 10 | import type { PartialOrd } from "./type-class/partial-ord.ts"; 11 | import type { SemiGroupoid } from "./type-class/semi-groupoid.ts"; 12 | 13 | export type Const = { 14 | readonly getConst: A; 15 | }; 16 | 17 | export const newConst = (value: A): Const => ({ getConst: value }); 18 | 19 | export const get = ({ getConst }: Const): A => getConst; 20 | 21 | export const partialEq = ( 22 | equality: PartialEq, 23 | ): PartialEq> => ({ 24 | eq: (l, r) => equality.eq(l.getConst, r.getConst), 25 | }); 26 | export const eq = (equality: Eq): Eq> => ({ 27 | eq: (l, r) => equality.eq(l.getConst, r.getConst), 28 | [eqSymbol]: true, 29 | }); 30 | export const partialOrd = ( 31 | order: PartialOrd, 32 | ): PartialOrd> => ({ 33 | ...partialEq(order), 34 | partialCmp: (l, r) => order.partialCmp(l.getConst, r.getConst), 35 | }); 36 | export const ord = (order: Ord): Ord> => ({ 37 | ...partialOrd(order), 38 | cmp: (l, r) => order.cmp(l.getConst, r.getConst), 39 | [eqSymbol]: true, 40 | }); 41 | 42 | export const partialEqUnary = ( 43 | equality: PartialEq, 44 | ): PartialEqUnary> => ({ 45 | liftEq: () => (l: Const, r: Const): boolean => 46 | equality.eq(l.getConst, r.getConst), 47 | }); 48 | 49 | export const compose = 50 | (_left: Const) => (right: Const): Const => right; 51 | 52 | export const biMap = 53 | (first: (a: A) => B) => 54 | (_second: (c: C) => D) => 55 | (curr: Const): Const => ({ getConst: first(curr.getConst) }); 56 | 57 | export const foldR = 58 | (_folder: (next: A) => (acc: B) => B) => 59 | (init: B) => 60 | (_data: Const): B => init; 61 | 62 | export interface ConstHkt extends Hkt2 { 63 | readonly type: Const; 64 | } 65 | 66 | export const semiGroupoid: SemiGroupoid = { 67 | compose, 68 | }; 69 | 70 | export const bifunctor: Bifunctor = { 71 | biMap, 72 | }; 73 | 74 | export const foldable: Foldable = { 75 | foldR, 76 | }; 77 | 78 | export const functor = (): Functor> => ({ 79 | map: () => (t) => t, 80 | }); 81 | 82 | export const applicative = ( 83 | monoid: Monoid, 84 | ): Applicative> => ({ 85 | ...functor(), 86 | pure: () => ({ getConst: monoid.identity }), 87 | apply: () => (t) => t, 88 | }); 89 | -------------------------------------------------------------------------------- /src/cont/monad.ts: -------------------------------------------------------------------------------- 1 | import type { Get1 } from "../hkt.ts"; 2 | import type { Monad } from "../type-class/monad.ts"; 3 | 4 | export type CallCC = ( 5 | continuation: (callback: (a: A) => Get1) => Get1, 6 | ) => Get1; 7 | 8 | export type MonadCont = Monad & { 9 | readonly callCC: CallCC; 10 | }; 11 | 12 | export const label = 13 | (mc: MonadCont) => (a: A): Get1 Get1, A]> => 14 | mc.callCC( 15 | ( 16 | k: (a: [(a: A) => Get1, A]) => Get1, 17 | ): Get1 Get1, A]> => { 18 | const go = (b: A) => k([go, b]); 19 | return mc.pure<[(a: A) => Get1, A]>([go, a]); 20 | }, 21 | ); 22 | 23 | export const labelWithoutArg = (mc: MonadCont): Get1> => 24 | mc.map((f: () => Get1) => f())( 25 | mc.callCC( 26 | ( 27 | k: (a: () => Get1) => Get1, 28 | ): Get1 Get1> => { 29 | const go = (): Get1 => k(go); 30 | return mc.pure(go); 31 | }, 32 | ), 33 | ); 34 | -------------------------------------------------------------------------------- /src/curry.ts: -------------------------------------------------------------------------------- 1 | type Equal = (() => T extends X ? 1 : 2) extends 2 | () => T extends Y ? 1 : 2 ? true 3 | : false; 4 | 5 | /** 6 | * Curried form of the function type `F`. 7 | */ 8 | export type Curried = F extends (...args: infer A) => infer R 9 | ? Equal R> extends true ? () => R 10 | : A extends [infer A1, ...infer S] ? (arg: A1) => Curried<(...rest: S) => R> 11 | : R 12 | : never; 13 | 14 | /** 15 | * Curries the n-ary function. 16 | * 17 | * @param fn - The function to be curried. 18 | * @returns The curried function. 19 | */ 20 | export function curry unknown>( 21 | fn: F, 22 | ): Curried { 23 | if (fn.length === 0) { 24 | return (() => fn()) as Curried; 25 | } 26 | const curried = 27 | (target: F, ...argStack: unknown[]) => (newArg: unknown) => { 28 | const totalArgs = [...argStack, newArg]; 29 | if (target.length <= totalArgs.length) { 30 | return target(...totalArgs); 31 | } 32 | return curried(target, ...totalArgs); 33 | }; 34 | return curried(fn) as Curried; 35 | } 36 | -------------------------------------------------------------------------------- /src/dual.ts: -------------------------------------------------------------------------------- 1 | import type { Fn, FnHkt } from "./func.ts"; 2 | import type { Get2, Hkt2 } from "./hkt.ts"; 3 | import { type AbelianGroup, abelSymbol } from "./type-class/abelian-group.ts"; 4 | import type { GenericBifunctor } from "./type-class/bifunctor.ts"; 5 | import type { Category } from "./type-class/category.ts"; 6 | import type { Group } from "./type-class/group.ts"; 7 | import type { Monoid } from "./type-class/monoid.ts"; 8 | import { semiGroupSymbol } from "./type-class/semi-group.ts"; 9 | import type { Contravariant } from "./type-class/variance.ts"; 10 | 11 | /** 12 | * Inverse of arrow from `A` to `B`. 13 | */ 14 | export type Dual = (b: B) => A; 15 | 16 | export interface DualHkt extends Hkt2 { 17 | readonly type: Dual; 18 | } 19 | 20 | /** 21 | * The instance of `Category` for `Dual`. The opposite category. 22 | */ 23 | export const cat: Category = { 24 | identity: () => (x) => x, 25 | compose: (funcA) => (funcB) => (c) => funcB(funcA(c)), 26 | }; 27 | 28 | /** 29 | * The instance of `Contravariant` for `Dual`. 30 | */ 31 | export const contra: Contravariant = { 32 | contraMap: 33 | (f: (t: T) => U) => (bDual: Dual): Dual => (t) => 34 | bDual(f(t)), 35 | }; 36 | 37 | /** 38 | * Creates the instance of `Monoid` from `A`'s one. 39 | * 40 | * @param m - The instance of `Monoid` for `A`. 41 | * @returns The instance of `Monoid` for `Dual`. 42 | */ 43 | export const monoid = (m: Monoid): Monoid> => ({ 44 | identity: () => m.identity, 45 | combine: (f, g) => (b) => m.combine(f(b), g(b)), 46 | [semiGroupSymbol]: true, 47 | }); 48 | 49 | /** 50 | * Creates the instance of `Bifunctor` from `Fn`'s one. 51 | * 52 | * @param m - The instance of `Bifunctor` for `Fn`. 53 | * @returns The instance of `Bifunctor` for `Dual`. 54 | */ 55 | export const bifunctor = ( 56 | bf: GenericBifunctor, 57 | ): GenericBifunctor => ({ 58 | cat1: cat, 59 | cat2: cat, 60 | cat3: cat, 61 | genericBiMap: 62 | (f: Dual) => 63 | (g: Dual): Dual, Get2> => 64 | bf.genericBiMap(f)(g), 65 | }); 66 | 67 | /** 68 | * @param g - The instance of `Group` for `A`. 69 | * @returns The instance of `Group` for `Dual`. 70 | */ 71 | export const group = (g: Group): Group> => ({ 72 | combine: (l, r) => (b) => g.combine(l(b), r(b)), 73 | identity: () => g.identity, 74 | invert: (f) => (b) => g.invert(f(b)), 75 | [semiGroupSymbol]: true, 76 | }); 77 | 78 | /** 79 | * @param g - The instance of `AbelianGroup` for `A`. 80 | * @returns The instance of `AbelianGroup` for `Dual`. 81 | */ 82 | export const abelianGroup = ( 83 | g: AbelianGroup, 84 | ): AbelianGroup> => ({ 85 | combine: (l, r) => (a) => g.combine(l(a), r(a)), 86 | identity: () => g.identity, 87 | invert: (f) => (a) => g.invert(f(a)), 88 | [semiGroupSymbol]: true, 89 | [abelSymbol]: true, 90 | }); 91 | -------------------------------------------------------------------------------- /src/envelope.ts: -------------------------------------------------------------------------------- 1 | import { doT } from "./cat.ts"; 2 | import { 3 | type Code, 4 | type Decoder, 5 | decUtf8, 6 | type Encoder, 7 | encUtf8, 8 | failDecoder, 9 | monadForCodeM, 10 | monadForDecoder, 11 | } from "./serial.ts"; 12 | 13 | /** 14 | * A envelope object to transport data into another machine. 15 | */ 16 | export type Envelope = Readonly<{ 17 | /** 18 | * The namespace URL of your data. It can be an URL to the scheme of your data format. 19 | */ 20 | namespace: string; 21 | /** 22 | * The packed payload object. 23 | */ 24 | payload: T; 25 | }>; 26 | 27 | /** 28 | * Packs a payload object into a new envelope. 29 | * 30 | * @param namespace - The namespace URL of your data. It can be an URL to the scheme of your data format. 31 | * @param payload - A payload object to be packed. 32 | * @returns The new envelope with `payload`. 33 | */ 34 | export const pack = (namespace: string) => (payload: T): Envelope => ({ 35 | namespace, 36 | payload, 37 | }); 38 | 39 | /** 40 | * Creates an `Encoder` for `Envelope` from a `Encoder`. 41 | * 42 | * @param encodeT - A payload encoder. 43 | * @returns The new encoder for `Envelope`. 44 | */ 45 | export const encode = 46 | (encodeT: Encoder): Encoder> => 47 | (env: Envelope): Code => 48 | doT(monadForCodeM) 49 | .run(encUtf8(env.namespace)) 50 | .finishM(() => encodeT(env.payload)); 51 | /** 52 | * Creates a `Decoder` for `Envelope` from the namespace and a `Decoder`. It fails when found a namespace that didn't equal to `namespace`. 53 | * 54 | * @param namespace - The expected namespace of data. 55 | * @param decodeT - A payload decoder. 56 | * @returns The new decoder for `Envelope`. 57 | */ 58 | export const decodeFor = 59 | (namespace: string) => (decodeT: Decoder): Decoder> => 60 | doT(monadForDecoder) 61 | .addM("actualNamespace", decUtf8()) 62 | .when( 63 | ({ actualNamespace }) => actualNamespace !== namespace, 64 | ({ actualNamespace }) => 65 | failDecoder( 66 | `expected namespace '${namespace}' but found '${actualNamespace}'`, 67 | ), 68 | ) 69 | .addM("payload", decodeT) 70 | .finish(({ payload }) => pack(namespace)(payload)); 71 | -------------------------------------------------------------------------------- /src/ether.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "../deps.ts"; 2 | import { cat } from "./cat.ts"; 3 | import { composeT, liftEther } from "./ether.ts"; 4 | import { 5 | compose, 6 | newEther, 7 | newEtherSymbol, 8 | newEtherT, 9 | runEther, 10 | runEtherT, 11 | } from "./ether.ts"; 12 | import { monad, type PromiseHkt } from "./promise.ts"; 13 | 14 | type Article = { 15 | createdAt: string; 16 | updatedAt: string; 17 | body: string; 18 | }; 19 | 20 | interface ArticleRepository { 21 | has: (id: string) => Promise; 22 | insert: (id: string, article: Partial
) => Promise; 23 | } 24 | const repoSymbol = newEtherSymbol(); 25 | 26 | type Req = { 27 | id: string; 28 | timestamp: string; 29 | body: string; 30 | }; 31 | const serviceSymbol = newEtherSymbol<(req: Req) => Promise>(); 32 | const service = newEther( 33 | serviceSymbol, 34 | ({ repo }) => async ({ id, timestamp, body }: Req) => { 35 | if (!await repo.has(id)) { 36 | return; 37 | } 38 | await repo.insert(id, { updatedAt: timestamp, body }); 39 | return; 40 | }, 41 | { 42 | repo: repoSymbol, 43 | }, 44 | ); 45 | 46 | Deno.test("runs an Ether", async () => { 47 | const mockRepository = newEther( 48 | repoSymbol, 49 | () => ({ 50 | has: (id) => { 51 | assertEquals(id, "foo"); 52 | return Promise.resolve(true); 53 | }, 54 | insert: (id, article) => { 55 | assertEquals(id, "foo"); 56 | assertEquals(article, { 57 | updatedAt: "2020-01-01T13:17:00Z", 58 | body: "Hello, World!", 59 | }); 60 | return Promise.resolve(); 61 | }, 62 | }), 63 | ); 64 | const injecting = compose(mockRepository); 65 | const ether = injecting(service); 66 | await runEther(ether)({ 67 | id: "foo", 68 | timestamp: "2020-01-01T13:17:00Z", 69 | body: "Hello, World!", 70 | }); 71 | }); 72 | 73 | Deno.test("deps on Promise", async () => { 74 | type TokenVerifier = { 75 | verify: (token: string) => Promise; 76 | }; 77 | const tokenVerifierSymbol = newEtherSymbol(); 78 | const tokenVerifier = newEtherT()( 79 | tokenVerifierSymbol, 80 | () => 81 | Promise.resolve({ 82 | verify: (token) => Promise.resolve(token.length === 3), 83 | }), 84 | ); 85 | 86 | type PrivateRepository = { 87 | fetch: () => Promise; 88 | }; 89 | const privateRepositorySymbol = newEtherSymbol(); 90 | const privateRepository = newEther(privateRepositorySymbol, () => ({ 91 | fetch: () => Promise.resolve({ flag: "YOU_ARE_AN_IDIOT" }), 92 | })); 93 | 94 | type PrivateFetcher = { 95 | fetch: (token: string) => Promise; 96 | }; 97 | const privateFetcherSymbol = newEtherSymbol(); 98 | const privateFetcher = newEther( 99 | privateFetcherSymbol, 100 | ({ verifier, repo }) => ({ 101 | fetch: async (token) => { 102 | if (!(await verifier.verify(token))) { 103 | throw new Error("token verification failure"); 104 | } 105 | return repo.fetch(); 106 | }, 107 | }), 108 | { 109 | verifier: tokenVerifierSymbol, 110 | repo: privateRepositorySymbol, 111 | }, 112 | ); 113 | 114 | const composePromise = composeT(monad); 115 | const liftPromise = liftEther(monad); 116 | const composed = await cat(liftPromise(privateFetcher)) 117 | .feed(composePromise(tokenVerifier)) 118 | .feed(composePromise(liftPromise(privateRepository))) 119 | .feed(runEtherT).value; 120 | 121 | assertEquals(await composed.fetch("foo"), { flag: "YOU_ARE_AN_IDIOT" }); 122 | }); 123 | -------------------------------------------------------------------------------- /src/exists.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * `Exists` represents an existential quantified type. It is useful to express the model that the object which decides what kind of object is held. 3 | * 4 | * @packageDocumentation 5 | * @module 6 | */ 7 | 8 | import type { Get1 } from "./hkt.ts"; 9 | 10 | declare const existsNominal: unique symbol; 11 | /** 12 | * `Exists` represents an existential quantified type. The detail of stored value is hidden and can be extracted only using `runExists`. 13 | */ 14 | export type Exists = F & { [existsNominal]: never }; 15 | 16 | /** 17 | * Converts any object that requires a type parameter into a existential quantified type. 18 | * 19 | * @param item - To be converted. 20 | * @returns The new existential quantified type. 21 | */ 22 | export const newExists = (item: Get1): Exists => 23 | item as Exists; 24 | 25 | /** 26 | * Extracts a value of the existential quantified type `F` with a runner. 27 | * 28 | * @param runner - A function to get the internal item `F`, but the type `A` will be specified by an object `Exists`. 29 | * @param exists - A value of the existential quantified type. 30 | * @returns The result of `runner`. 31 | */ 32 | export const runExists = ( 33 | runner: (item: Get1) => R, 34 | ): (exists: Exists) => R => runner as (exists: Exists) => R; 35 | -------------------------------------------------------------------------------- /src/free/monad.ts: -------------------------------------------------------------------------------- 1 | import { type FreeHkt, monad as monadF, wrap } from "../free.ts"; 2 | import { compose } from "../func.ts"; 3 | import type { Apply2Only, Get1, Get2 } from "../hkt.ts"; 4 | import type { MonadTrans, MonadTransHkt } from "../trans.ts"; 5 | import type { Functor } from "../type-class/functor.ts"; 6 | import { flat, type Monad } from "../type-class/monad.ts"; 7 | 8 | export type MonadFree = Monad & { 9 | readonly wrap: (fma: Get1>) => Get1; 10 | }; 11 | 12 | export const liftF = 13 | (functor: Functor, monadFree: MonadFree) => 14 | (fa: Get1): Get1 => 15 | monadFree.wrap(functor.map(monadFree.pure)(fa)); 16 | 17 | export const wrapT = ( 18 | functor: Functor, 19 | monadFree: MonadFree, 20 | trans: MonadTrans, 21 | monad: Monad>, 22 | ): (ftma: Get1>) => Get2 => 23 | compose>, Get2>(flat(monad))( 24 | compose, Get2>(trans.lift(monadFree))( 25 | liftF(functor, monadFree), 26 | ), 27 | ); 28 | 29 | export const functorMonadFree = (): MonadFree< 30 | F, 31 | Apply2Only 32 | > => ({ 33 | ...monadF(), 34 | wrap, 35 | }); 36 | -------------------------------------------------------------------------------- /src/frozen.ts: -------------------------------------------------------------------------------- 1 | import { id } from "./func.ts"; 2 | import type { Hkt1 } from "./hkt.ts"; 3 | import { andThen, none, type Option, some } from "./option.ts"; 4 | import { and as then, isEq, type Ordering } from "./ordering.ts"; 5 | import { type Eq, fromEquality } from "./type-class/eq.ts"; 6 | import type { Monad } from "./type-class/monad.ts"; 7 | import { fromCmp, type Ord } from "./type-class/ord.ts"; 8 | import { 9 | fromPartialEquality, 10 | type PartialEq, 11 | } from "./type-class/partial-eq.ts"; 12 | import { fromPartialCmp, type PartialOrd } from "./type-class/partial-ord.ts"; 13 | 14 | /** 15 | * The frozen type makes `T` type `readonly` recursively. 16 | */ 17 | export type Frozen = 18 | & T 19 | & { 20 | readonly [K in keyof T]: T[K] extends Frozen ? I 21 | : T[K] extends () => unknown ? T[K] 22 | : T[K] extends object ? Frozen 23 | : T[K]; 24 | }; 25 | 26 | export const partialEquality = 27 | (equalityDict: { readonly [K in keyof S]: PartialEq }) => 28 | (l: Frozen, r: Frozen): boolean => 29 | Object.keys(equalityDict).every((key) => { 30 | if (Object.hasOwn(equalityDict, key)) { 31 | const castKey = key as keyof S; 32 | return equalityDict[castKey].eq(l[castKey], r[castKey]); 33 | } 34 | return true; 35 | }); 36 | export const partialEq: ( 37 | equalityDict: { readonly [K in keyof S]: PartialEq }, 38 | ) => PartialEq> = fromPartialEquality(partialEquality); 39 | export const equality = 40 | (equalityDict: { readonly [K in keyof S]: Eq }) => 41 | (l: Frozen, r: Frozen): boolean => 42 | Object.keys(equalityDict).every((key) => { 43 | if (Object.hasOwn(equalityDict, key)) { 44 | const castKey = key as keyof S; 45 | return equalityDict[castKey].eq(l[castKey], r[castKey]); 46 | } 47 | return true; 48 | }); 49 | export const eq: ( 50 | equalityDict: { readonly [K in keyof S]: Eq }, 51 | ) => Eq> = fromEquality(equality); 52 | export const partialCmp = 53 | (orderDict: { readonly [K in keyof S]: PartialOrd }) => 54 | (l: Frozen, r: Frozen): Option => 55 | Object.keys(orderDict) 56 | .map((key) => { 57 | if (Object.hasOwn(orderDict, key)) { 58 | const castKey = key as keyof S; 59 | return orderDict[castKey].partialCmp( 60 | l[castKey], 61 | r[castKey], 62 | ); 63 | } 64 | return none(); 65 | }) 66 | .reduce((prev, curr) => 67 | andThen(( 68 | previous: Ordering, 69 | ) => (isEq(previous) ? curr : some(previous)))(prev) 70 | ); 71 | export const partialOrd: ( 72 | orderDict: { readonly [K in keyof S]: PartialOrd }, 73 | ) => PartialOrd> = fromPartialCmp(partialCmp); 74 | export const cmp = 75 | (orderDict: { readonly [K in keyof S]: Ord }) => 76 | (l: Frozen, r: Frozen): Ordering => 77 | Object.keys(orderDict) 78 | .map((key) => { 79 | if (Object.hasOwn(orderDict, key)) { 80 | const castKey = key as keyof S; 81 | return orderDict[castKey].cmp(l[castKey], r[castKey]); 82 | } 83 | throw new Error("`orderDict` must have comparator by own key"); 84 | }) 85 | .reduce((prev, curr) => then(curr)(prev)); 86 | export const ord: ( 87 | orderDict: { readonly [K in keyof S]: Ord }, 88 | ) => Ord> = fromCmp(cmp); 89 | 90 | /** 91 | * Freeze the value by casting as a `Frozen`. 92 | * 93 | * @param x - The value to be converted. 94 | * @returns The frozen value. 95 | */ 96 | export const freeze = (x: T): Frozen => x as Frozen; 97 | 98 | export const product = 99 | (fa: Frozen) => (fb: Frozen): Frozen<[A, B]> => 100 | freeze([fa, fb]); 101 | export const pure = freeze; 102 | export const map = (fn: (t: T) => U) => (ft: Frozen): Frozen => 103 | freeze(fn(ft)); 104 | export const flatMap = id; 105 | export const apply = map; 106 | 107 | export interface FrozenHkt extends Hkt1 { 108 | readonly type: Frozen; 109 | } 110 | 111 | /** 112 | * The instance of `Monad` for `Frozen`. 113 | */ 114 | export const monad: Monad = { 115 | pure, 116 | map, 117 | flatMap, 118 | apply, 119 | }; 120 | -------------------------------------------------------------------------------- /src/hkt.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Type of order 0. `*`. 3 | */ 4 | export interface Hkt0 { 5 | readonly type: unknown; 6 | } 7 | /** 8 | * Type of order 1. `arg1 -> *`. 9 | */ 10 | export interface Hkt1 extends Hkt0 { 11 | readonly arg1: unknown; 12 | } 13 | /** 14 | * Type of order 2. `arg2 -> arg1 -> *`. 15 | */ 16 | export interface Hkt2 extends Hkt1 { 17 | readonly arg2: unknown; 18 | } 19 | /** 20 | * Type of order 3. `arg3 -> arg2 -> arg1 -> *`. 21 | */ 22 | export interface Hkt3 extends Hkt2 { 23 | readonly arg3: unknown; 24 | } 25 | /** 26 | * Type of order 4. `arg4 -> arg3 -> arg2 -> arg1 -> *`. 27 | */ 28 | export interface Hkt4 extends Hkt3 { 29 | readonly arg4: unknown; 30 | } 31 | /** 32 | * Type of order 5. `arg5 -> arg4 -> arg3 -> arg2 -> arg1 -> *`. 33 | */ 34 | export interface Hkt5 extends Hkt4 { 35 | readonly arg5: unknown; 36 | } 37 | 38 | /** 39 | * Applies the first type parameter to HKT. 40 | */ 41 | export type Apply1 = S extends Hkt1 ? S & { 42 | readonly arg1: A1; 43 | } 44 | : never; 45 | /** 46 | * Applies the second type parameter to HKT. 47 | */ 48 | export type Apply2Only = S extends Hkt2 ? S & { 49 | readonly arg2: A2; 50 | } 51 | : never; 52 | /** 53 | * Applies the first and second type parameter to HKT. 54 | */ 55 | export type Apply2 = Apply1 & Apply2Only; 56 | /** 57 | * Applies the third type parameter to HKT. 58 | */ 59 | export type Apply3Only = S extends Hkt3 ? S & { 60 | readonly arg3: A3; 61 | } 62 | : never; 63 | /** 64 | * Applies the first, second and third type parameter to HKT. 65 | */ 66 | export type Apply3 = Apply2 & Apply3Only; 67 | /** 68 | * Applies the fourth type parameter to HKT. 69 | */ 70 | export type Apply4Only = S extends Hkt4 ? S & { 71 | readonly arg4: A4; 72 | } 73 | : never; 74 | /** 75 | * Applies the first, second, third and fourth type parameter to HKT. 76 | */ 77 | export type Apply4 = 78 | & Apply3 79 | & Apply4Only; 80 | /** 81 | * Applies the fifth type parameter to HKT. 82 | */ 83 | export type Apply5Only = S extends Hkt5 ? S & { 84 | readonly arg4: A5; 85 | } 86 | : never; 87 | /** 88 | * Applies the first, second, third, fourth and fifth type parameter to HKT. 89 | */ 90 | export type Apply5 = 91 | & Apply4 92 | & Apply5Only; 93 | 94 | /** 95 | * Gets the applied type of HKT. 96 | */ 97 | export type Instance = S extends Hkt0 ? S["type"] : never; 98 | 99 | /** 100 | * Applies one type parameter and gets the applied type of HKT. 101 | */ 102 | export type Get1 = Instance>; 103 | /** 104 | * Applies two type parameters and gets the applied type of HKT. 105 | */ 106 | export type Get2 = Instance>; 107 | /** 108 | * Applies three type parameters and gets the applied type of HKT. 109 | */ 110 | export type Get3 = Instance>; 111 | /** 112 | * Applies four type parameters and gets the applied type of HKT. 113 | */ 114 | export type Get4 = Instance>; 115 | /** 116 | * Applies five type parameters and gets the applied type of HKT. 117 | */ 118 | export type Get5 = Instance< 119 | Apply5 120 | >; 121 | -------------------------------------------------------------------------------- /src/identity.ts: -------------------------------------------------------------------------------- 1 | import { flip, id } from "./func.ts"; 2 | import type { Hkt1 } from "./hkt.ts"; 3 | import type { Applicative } from "./type-class/applicative.ts"; 4 | import type { Comonad } from "./type-class/comonad.ts"; 5 | import type { Distributive } from "./type-class/distributive.ts"; 6 | import type { Functor } from "./type-class/functor.ts"; 7 | import type { Monad } from "./type-class/monad.ts"; 8 | import type { PartialEqUnary } from "./type-class/partial-eq.ts"; 9 | import type { Settable } from "./type-class/settable.ts"; 10 | import type { Traversable } from "./type-class/traversable.ts"; 11 | 12 | export { id }; 13 | 14 | export interface IdentityHkt extends Hkt1 { 15 | readonly type: this["arg1"]; 16 | } 17 | 18 | /** 19 | * The identity functor which is same as the type parameter. 20 | */ 21 | export type Identity = T; 22 | 23 | /** 24 | * Gets the value of `Identity`. It is same as the identity function. 25 | */ 26 | export const run = id; 27 | 28 | /** 29 | * The instance of `Functor` for `Identity`. 30 | */ 31 | export const functor: Functor = { 32 | map: id, 33 | }; 34 | 35 | /** 36 | * The `Applicative` instance for `Identity`. 37 | */ 38 | export const applicative: Applicative = { 39 | pure: id, 40 | map: id, 41 | apply: id, 42 | }; 43 | 44 | /** 45 | * The instance of `Monad` for `Identity`. 46 | */ 47 | export const monad: Monad = { 48 | pure: id, 49 | map: id, 50 | flatMap: id, 51 | apply: id, 52 | }; 53 | 54 | /** 55 | * The instance of `Comonad` for `Identity`. 56 | */ 57 | export const comonad: Comonad = { 58 | map: id, 59 | extract: id, 60 | duplicate: id, 61 | }; 62 | 63 | /** 64 | * The instance of `Traversable` for `Identity`. 65 | */ 66 | export const traversable: Traversable = { 67 | map: id, 68 | foldR: flip, 69 | traverse: () => id, 70 | }; 71 | 72 | /** 73 | * The instance of `Functor` for `Identity`. 74 | */ 75 | export const distributive: Distributive = { 76 | map: id, 77 | distribute: () => id, 78 | }; 79 | 80 | /** 81 | * The instance of `Settable` for `Identity`. 82 | */ 83 | export const settable: Settable = { 84 | ...traversable, 85 | ...monad, 86 | ...distributive, 87 | untainted: id, 88 | }; 89 | 90 | /** 91 | * The `PartialEqUnary` instance for `Identity`. 92 | */ 93 | export const partialEqUnary: PartialEqUnary = { liftEq: id }; 94 | -------------------------------------------------------------------------------- /src/kleisli.ts: -------------------------------------------------------------------------------- 1 | import type { Apply3Only, Get1, Hkt3 } from "./hkt.ts"; 2 | import type { Category } from "./type-class/category.ts"; 3 | import type { Monad } from "./type-class/monad.ts"; 4 | 5 | /** 6 | * The kleisli arrow from `A` to `B` in `M`. 7 | */ 8 | export type Kleisli = { 9 | readonly runKleisli: (a: A) => Get1; 10 | }; 11 | 12 | export interface KleisliHkt extends Hkt3 { 13 | readonly type: Kleisli; 14 | } 15 | 16 | /** 17 | * The instance of `Category` for `Kleisli` from monad `M`. 18 | */ 19 | export const category = ( 20 | monad: Monad, 21 | ): Category> => ({ 22 | identity: () => ({ 23 | runKleisli: monad.pure, 24 | }), 25 | compose: ({ runKleisli: g }) => ({ runKleisli: f }) => ({ 26 | runKleisli: (a) => monad.flatMap(g)(f(a)), 27 | }), 28 | }); 29 | -------------------------------------------------------------------------------- /src/multi-set.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "../deps.ts"; 2 | import { 3 | contains, 4 | count, 5 | empty, 6 | insert, 7 | intoMut, 8 | len, 9 | remove, 10 | } from "./multi-set.ts"; 11 | import { doMut, readMutRef } from "./mut.ts"; 12 | 13 | Deno.test("len", () => { 14 | assertEquals(len(empty()), 0); 15 | 16 | const one = doMut((cat) => 17 | cat.addM("set", intoMut(empty())) 18 | .runWith(({ set }) => insert(1)(set)) 19 | .finishM(({ set }) => readMutRef(set)) 20 | ); 21 | assertEquals(len(one), 1); 22 | }); 23 | 24 | Deno.test("contains", () => { 25 | assertEquals(contains(0)(empty()), false); 26 | assertEquals(contains(1)(empty()), false); 27 | 28 | const one = doMut((cat) => 29 | cat.addM("set", intoMut(empty())) 30 | .runWith(({ set }) => insert(1)(set)) 31 | .finishM(({ set }) => readMutRef(set)) 32 | ); 33 | assertEquals(contains(0)(one), false); 34 | assertEquals(contains(1)(one), true); 35 | assertEquals(contains(2)(one), false); 36 | }); 37 | 38 | Deno.test("count", () => { 39 | assertEquals(count(0)(empty()), 0); 40 | assertEquals(count(1)(empty()), 0); 41 | 42 | const one = doMut((cat) => 43 | cat.addM("set", intoMut(empty())) 44 | .runWith(({ set }) => insert(1)(set)) 45 | .finishM(({ set }) => readMutRef(set)) 46 | ); 47 | assertEquals(count(0)(one), 0); 48 | assertEquals(count(1)(one), 1); 49 | assertEquals(count(2)(one), 0); 50 | 51 | const oneTwoTwo = doMut((cat) => 52 | cat.addM("set", intoMut(empty())) 53 | .runWith(({ set }) => insert(1)(set)) 54 | .runWith(({ set }) => insert(2)(set)) 55 | .runWith(({ set }) => insert(2)(set)) 56 | .addMWith("_", ({ set }) => remove(2)(set)) 57 | .runWith(({ set }) => insert(2)(set)) 58 | .finishM(({ set }) => readMutRef(set)) 59 | ); 60 | assertEquals(count(0)(oneTwoTwo), 0); 61 | assertEquals(count(1)(oneTwoTwo), 1); 62 | assertEquals(count(2)(oneTwoTwo), 2); 63 | assertEquals(count(3)(oneTwoTwo), 0); 64 | }); 65 | -------------------------------------------------------------------------------- /src/multi-set.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This module provides a multi-set by the counting method. 3 | * 4 | * @packageDocumentation 5 | * @module 6 | */ 7 | 8 | import { 9 | mapMut, 10 | modifyMutRef, 11 | type Mut, 12 | type MutRef, 13 | newMutRef, 14 | readMutRef, 15 | } from "./mut.ts"; 16 | 17 | export type MultiSetInner = { 18 | /** 19 | * The numbers of items stored in. The count is always positive. 20 | */ 21 | counts: Map; 22 | /** 23 | * The total number of items stored in. 24 | */ 25 | len: number; 26 | }; 27 | /** 28 | * A multi-set by the counting method. 29 | */ 30 | export type MultiSet = Readonly>; 31 | 32 | /** 33 | * Creates a new empty multi-set. 34 | * 35 | * @returns The new multi-set. 36 | */ 37 | export const empty = (): MultiSet => ({ 38 | counts: new Map(), 39 | len: 0, 40 | }); 41 | 42 | /** 43 | * Gets the length of items stored in the multi-set. 44 | * 45 | * @param set - To be queried. 46 | * @returns The number of items stored in. 47 | */ 48 | export const len = (set: MultiSet): number => set.len; 49 | 50 | /** 51 | * Checks whether the item is stored in the multi-set. 52 | * 53 | * @param item - To check. 54 | * @param set - To be checked. 55 | * @returns Whether `items` is contained. 56 | */ 57 | export const contains = (item: T) => (set: MultiSet): boolean => 58 | set.counts.has(item); 59 | 60 | /** 61 | * Gets the number of `item`s stored in the multi-set. 62 | * 63 | * @param item - To query. 64 | * @param set - To be queried. 65 | * @returns The number of items stored. 66 | */ 67 | export const count = (item: T) => (set: MultiSet): number => 68 | set.counts.get(item) ?? 0; 69 | 70 | /** 71 | * Makes the multi-set into a mutable variable. 72 | * 73 | * @param set - Source data. 74 | * @returns The mutable multi-set in `Mut` environment. 75 | */ 76 | export const intoMut = ( 77 | set: MultiSet, 78 | ): Mut>> => 79 | newMutRef({ 80 | counts: new Map(set.counts), 81 | len: set.len, 82 | }); 83 | 84 | /** 85 | * Inserts the item to the multi-set on `Mut`. 86 | * 87 | * @param item - To be inserted. 88 | * @param ref - The reference to a mutable multi-set. 89 | * @returns The inserting operation. 90 | */ 91 | export const insert = 92 | (item: T) => (ref: MutRef>): Mut => 93 | modifyMutRef(ref)((set) => { 94 | if (set.counts.has(item)) { 95 | set.counts.set(item, set.counts.get(item)! + 1); 96 | } else { 97 | set.counts.set(item, 1); 98 | } 99 | set.len += 1; 100 | return set; 101 | }); 102 | 103 | /** 104 | * Removes the items from the multi-set on `Mut`. 105 | * 106 | * @param item - To be removed. 107 | * @param ref - The reference to a mutable multi-set. 108 | * @returns Whether removing succeeded. 109 | */ 110 | export const remove = 111 | (item: T) => (ref: MutRef>): Mut => 112 | mapMut((set: MultiSetInner) => { 113 | if (set.counts.get(item) === 1) { 114 | set.counts.delete(item); 115 | set.len -= 1; 116 | return true; 117 | } 118 | if (set.counts.has(item)) { 119 | set.counts.set(item, set.counts.get(item)! - 1); 120 | set.len -= 1; 121 | return true; 122 | } 123 | return false; 124 | })(readMutRef(ref)); 125 | -------------------------------------------------------------------------------- /src/number.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "../deps.ts"; 2 | import { partialCmp } from "./number.ts"; 3 | import { none, some } from "./option.ts"; 4 | import { equal, greater, less, type Ordering } from "./ordering.ts"; 5 | 6 | Deno.test("partialCmp", () => { 7 | assertEquals(partialCmp(1, NaN), none()); 8 | assertEquals(partialCmp(NaN, 2), none()); 9 | assertEquals(partialCmp(NaN, NaN), none()); 10 | assertEquals(partialCmp(1, 1), some(equal as Ordering)); 11 | assertEquals(partialCmp(2, 2), some(equal as Ordering)); 12 | assertEquals(partialCmp(1, 2), some(less as Ordering)); 13 | assertEquals(partialCmp(2, 1), some(greater as Ordering)); 14 | }); 15 | -------------------------------------------------------------------------------- /src/number.ts: -------------------------------------------------------------------------------- 1 | import { none, type Option, some } from "./option.ts"; 2 | import { equal, greater, less, type Ordering } from "./ordering.ts"; 3 | import { 4 | type AbelianGroup, 5 | type AbelianGroupExceptZero, 6 | abelSymbol, 7 | } from "./type-class/abelian-group.ts"; 8 | import type { Field } from "./type-class/field.ts"; 9 | import { fromPartialCmp, type PartialOrd } from "./type-class/partial-ord.ts"; 10 | import type { Ring } from "./type-class/ring.ts"; 11 | import { semiGroupSymbol } from "./type-class/semi-group.ts"; 12 | 13 | export const partialCmp = (lhs: number, rhs: number): Option => { 14 | if (Number.isNaN(lhs) || Number.isNaN(rhs)) { 15 | return none(); 16 | } 17 | if (lhs === rhs) { 18 | return some(equal); 19 | } 20 | if (lhs < rhs) { 21 | return some(less); 22 | } 23 | return some(greater); 24 | }; 25 | export const partialOrd: PartialOrd = fromPartialCmp(() => 26 | partialCmp 27 | )(); 28 | 29 | export const addAbelianGroup: AbelianGroup = { 30 | combine: (l, r) => l + r, 31 | identity: 0, 32 | invert: (g) => -g, 33 | [semiGroupSymbol]: true, 34 | [abelSymbol]: true, 35 | }; 36 | 37 | export const mulAbelianGroup: AbelianGroupExceptZero = { 38 | combine: (l, r) => l * r, 39 | identity: 1, 40 | invert: (g) => (g === 0 ? none() : some(1 / g)), 41 | [semiGroupSymbol]: true, 42 | [abelSymbol]: true, 43 | }; 44 | 45 | export const ring: Ring = { 46 | additive: addAbelianGroup, 47 | multiplication: mulAbelianGroup, 48 | }; 49 | 50 | export const field: Field = { 51 | additive: addAbelianGroup, 52 | multiplication: mulAbelianGroup, 53 | }; 54 | -------------------------------------------------------------------------------- /src/optical/getter.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @packageDocumentation 3 | * @module 4 | * Extraction combinator for a data structure. 5 | * ```text 6 | * S --[ extract ]--> A 7 | 8 | * T <--------------- T 9 | * ``` 10 | */ 11 | 12 | import type { Optic } from "../optical.ts"; 13 | 14 | export type Getter = Optic; 15 | 16 | export const newGetter = 17 | (getter: (s: S) => A): Getter => 18 | (next) => 19 | (received) => 20 | (callback) => next(getter(received))(callback); 21 | -------------------------------------------------------------------------------- /src/optical/lens.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "../../deps.ts"; 2 | import { opticCat } from "../optical.ts"; 3 | import { key, keys, nth } from "./lens.ts"; 4 | 5 | Deno.test("deep structure", () => { 6 | const hoge = { 7 | a: 0, 8 | fuga: { 9 | b: "hoge", 10 | piyo: [42], 11 | }, 12 | }; 13 | 14 | assertEquals( 15 | opticCat(hoge).feed(key("fuga")).feed(key("piyo")).feed(nth(0)) 16 | .unwrap(), 17 | 42, 18 | ); 19 | assertEquals( 20 | opticCat(hoge).feed(key("fuga")).feed(key("piyo")).feed(nth(0)).set(31), 21 | { 22 | a: 0, 23 | fuga: { 24 | b: "hoge", 25 | piyo: [31], 26 | }, 27 | }, 28 | ); 29 | }); 30 | 31 | Deno.test("modify only x", () => { 32 | const coord = { x: 2.5, y: 3 }; 33 | assertEquals( 34 | opticCat(coord) 35 | .feed(key("x")) 36 | .over((x) => x - 1), 37 | { 38 | x: 1.5, 39 | y: 3, 40 | }, 41 | ); 42 | }); 43 | 44 | Deno.test("keys", () => { 45 | const record = { 46 | foo: 4, 47 | bar: "xyz", 48 | baz: 7, 49 | }; 50 | const updated = opticCat(record) 51 | .feed(keys(["foo", "bar"])) 52 | .over((entries): [["foo", number], ["bar", string]] => 53 | entries.map(( 54 | entry: ["foo", number] | ["bar", string], 55 | ): ["foo", number] | ["bar", string] => 56 | entry[0] === "foo" 57 | ? [ 58 | entry[0], 59 | entry[1] + 1, 60 | ] 61 | : [ 62 | entry[0], 63 | "vw " + entry[1], 64 | ] 65 | ) as [["foo", number], ["bar", string]] 66 | ); 67 | assertEquals(updated, { 68 | foo: 5, 69 | bar: "vw xyz", 70 | baz: 7, 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /src/optical/lens.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @packageDocumentation 3 | * @module 4 | * Transformation combinator for a data structure. 5 | * ```text 6 | * S -----|--[ get ]-> A 7 | * V 8 | * T <-[ set ]<------- B 9 | * ``` 10 | */ 11 | 12 | import type { Optic } from "../optical.ts"; 13 | 14 | /** 15 | * Creates a new `Lens` optic from the two functions. 16 | * 17 | * @param get - The extraction process. 18 | * @param set - The overwrite process. 19 | * @returns The computation to focus the data. 20 | */ 21 | export const newLens = 22 | (get: (s: S) => A) => 23 | (set: (s: S) => (b: B) => T): Optic => 24 | (next) => 25 | (received) => 26 | (callback) => next(get(received))((b) => callback(set(received)(b))); 27 | 28 | /** 29 | * Focuses to the given index of array. 30 | * 31 | * @param index - The index of array to extract. 32 | * @returns The lens for indexing. 33 | */ 34 | export const nth = < 35 | const I extends number, 36 | Tuple extends readonly unknown[], 37 | V = Tuple[I], 38 | >( 39 | index: I, 40 | ): Optic => 41 | newLens((source) => source[index])( 42 | (source) => (part) => 43 | [ 44 | ...source.slice(0, index), 45 | part, 46 | ...source.slice(index + 1), 47 | ] as unknown as Tuple, 48 | ); 49 | 50 | /** 51 | * Focuses to the given key of object. 52 | * 53 | * @param k - The key of object to extract. 54 | * @returns The lens for indexing. 55 | */ 56 | export const key = < 57 | const K extends PropertyKey, 58 | O extends Readonly>, 59 | V = O[K], 60 | >( 61 | k: K, 62 | ): Optic => 63 | newLens((source) => source[k])((source) => (part) => ({ 64 | ...source, 65 | [k]: part, 66 | })); 67 | 68 | export type Entries = K extends readonly [infer H, ...infer R] 69 | ? H extends keyof O ? [R] extends [[]] ? [[H, O[H]]] 70 | : [[H, O[H]], ...Entries] 71 | : never 72 | : [PropertyKey, unknown][]; 73 | 74 | /** 75 | * Focuses to the given keys of object. 76 | * 77 | * @param k - The keys array of object to extract. 78 | * @returns The lens for indexing. 79 | */ 80 | export const keys = < 81 | const K extends readonly PropertyKey[], 82 | O extends Readonly>, 83 | >( 84 | keysToUpdate: K, 85 | ): Optic, Entries> => 86 | newLens>( 87 | (source) => 88 | keysToUpdate.map((k: K[number]) => [k, source[k]]) as Entries, 89 | )((source) => (parts: Entries) => { 90 | const obj = { ...source } as Record; 91 | for (const [k, v] of parts) { 92 | if (Object.hasOwn(obj, k)) { 93 | obj[k] = v; 94 | } 95 | } 96 | return obj as O; 97 | }); 98 | -------------------------------------------------------------------------------- /src/optical/parallel.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @packageDocumentation 3 | * @module 4 | * 5 | * A parallel combinator that merging two computations `C1` and `C2`. 6 | * 7 | * ```text 8 | * +------+ A1 9 | * S --------------+----->| |--------------+ 10 | * | T1 | C1 | | 11 | * +------|------| |<------+ V 12 | * | | +------+ | [ joinA ]--> A 13 | * V | | ^ 14 | * T <--[ joinT ] | +------+ A2 | | 15 | * ^ |----->| |-------|------+ 16 | * | T2 | C2 | | 17 | * +-------------| |<------+-------------- B 18 | * +------+ 19 | * ``` 20 | */ 21 | 22 | import { absurd } from "../func.ts"; 23 | import type { Optic } from "../optical.ts"; 24 | 25 | /** 26 | * Creates a new parallel combinator that merging two computations `C1` and `C2`. 27 | * 28 | * ```text 29 | * +------+ A1 30 | * S --------------+----->| |--------------+ 31 | * | T1 | C1 | | 32 | * +------|------| |<------+ V 33 | * | | +------+ | [ joinA ]--> A 34 | * V | | ^ 35 | * T <--[ joinT ] | +------+ A2 | | 36 | * ^ |----->| |-------|------+ 37 | * | T2 | C2 | | 38 | * +-------------| |<------+-------------- B 39 | * +------+ 40 | * ``` 41 | */ 42 | export const newParallel = 43 | (joinT: (t1: T1) => (t2: T2) => T) => 44 | (joinA: (a1: A1) => (a2: A2) => A) => 45 | (computation1: Optic) => 46 | (computation2: Optic): Optic => 47 | (next: (sending: A) => (continuation: (returned: B) => R) => R) => 48 | (received: S) => 49 | (callback: (t: T) => R): R => 50 | computation1((sending1) => (continuation1) => 51 | computation2((sending2) => () => 52 | next(joinA(sending1)(sending2))(continuation1) 53 | )(received)(absurd) 54 | )(received)((t1) => 55 | computation2((sending2) => (continuation2) => 56 | computation1((sending1) => () => 57 | next(joinA(sending1)(sending2))(continuation2) 58 | )(received)(absurd) 59 | )(received)((t2) => callback(joinT(t1)(t2))) 60 | ); 61 | -------------------------------------------------------------------------------- /src/optical/prism.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "../../deps.ts"; 2 | import { opticCat } from "../optical.ts"; 3 | import { ifSome, none, type Option, some } from "../option.ts"; 4 | import { key } from "./lens.ts"; 5 | import { filter } from "./prism.ts"; 6 | import { only, unreachable } from "./prism.ts"; 7 | 8 | Deno.test("optional", () => { 9 | const obj = { 10 | foo: "x", 11 | hoge: some({ 12 | bar: 2, 13 | fuga: some(12), 14 | }) as Option<{ bar: number; fuga: Option }>, 15 | }; 16 | assertEquals( 17 | opticCat(obj).feed(key("hoge")).feed(ifSome()).feed(key("fuga")).feed( 18 | ifSome(), 19 | ).set(42), 20 | { 21 | foo: "x", 22 | hoge: some({ 23 | bar: 2, 24 | fuga: some(42), 25 | }), 26 | }, 27 | ); 28 | }); 29 | 30 | Deno.test("unreachable", () => { 31 | assertEquals(opticCat(4).feed(unreachable()).get(), none()); 32 | }); 33 | 34 | Deno.test("only", () => { 35 | assertEquals(opticCat(4).feed(only(4)).get(), some(4)); 36 | assertEquals(opticCat(4).feed(only(5)).get(), none()); 37 | assertEquals(opticCat(4).feed(only(4)).set(6), 6); 38 | assertEquals(opticCat(4).feed(only(5)).set(6), 4); 39 | }); 40 | 41 | Deno.test("filter", () => { 42 | assertEquals( 43 | opticCat(6).feed(filter((x: number) => x % 2 === 0)).get(), 44 | some(6), 45 | ); 46 | assertEquals( 47 | opticCat(7).feed(filter((x: number) => x % 2 === 0)).get(), 48 | none(), 49 | ); 50 | assertEquals( 51 | opticCat(6).feed(filter((x: number) => x % 2 === 0)).set(8), 52 | 8, 53 | ); 54 | assertEquals( 55 | opticCat(7).feed(filter((x: number) => x % 2 === 0)).set(8), 56 | 7, 57 | ); 58 | }); 59 | -------------------------------------------------------------------------------- /src/optical/prism.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @packageDocumentation 3 | * @module 4 | * Transformation combinator for a data enumerator. 5 | * ```text 6 | * ok 7 | * S --[ downcast ]--|-------------> A 8 | * | 9 | * | err 10 | * V 11 | * T <---------------O<-[ upcast ]-- B 12 | * ``` 13 | */ 14 | 15 | import { absurd } from "../func.ts"; 16 | import type { Optic } from "../optical.ts"; 17 | import { none, okOr, type Option, some } from "../option.ts"; 18 | import { either, err, type Result } from "../result.ts"; 19 | 20 | /** 21 | * Creates a new `Prism` optic from the two functions. 22 | * 23 | * @param upcast - The function which coerces the modified value to a partial type. 24 | * @param downcast - The function which tries to coerces the source value. 25 | * @returns The computation to focus the data. 26 | */ 27 | export const newPrism = 28 | (upcast: (b: B) => T) => 29 | (downcast: (s: S) => Result): Optic => 30 | (next) => 31 | (received) => 32 | (callback) => 33 | either(callback)((a: A) => next(a)((b) => callback(upcast(b))))( 34 | downcast(received), 35 | ); 36 | 37 | /** 38 | * Creates a new `Prism` optic from the two functions, but `downcast` may return a `Option`. 39 | * 40 | * @param upcast - The function which coerces the modified value to a partial type. 41 | * @param downcast - The function which tries to coerces the source value. 42 | * @returns The computation to focus the data. 43 | */ 44 | export const newPrismSimple = 45 | (upcast: (b: B) => S) => 46 | (downcast: (s: S) => Option): Optic => 47 | newPrism(upcast)((s) => okOr(s)(downcast(s))); 48 | 49 | /** 50 | * @returns The optic which matches nothing. Setting a value through this will throw an error (but will be probably a type error). 51 | */ 52 | export const unreachable = (): Optic => 53 | newPrism(absurd)(err); 54 | 55 | /** 56 | * Filters the value only if equals to `target`. 57 | * 58 | * @param target - For comparison. 59 | * @returns The computation to filter the data. 60 | */ 61 | export const only = (target: A): Optic => 62 | newPrismSimple((newValue) => newValue)(( 63 | x, 64 | ) => (x === target ? some(x) : none())); 65 | 66 | /** 67 | * Filters the value only if satisfies `pred`. 68 | * 69 | * @param pred - Condition to filter. 70 | * @returns The computation to filter the data. 71 | */ 72 | export const filter = (pred: (a: A) => boolean): Optic => 73 | newPrismSimple((newValue) => newValue)(( 74 | x, 75 | ) => (pred(x) ? some(x) : none())); 76 | -------------------------------------------------------------------------------- /src/optical/retriable.test.ts: -------------------------------------------------------------------------------- 1 | import { exponentialBackoff } from "./retriable.ts"; 2 | import { opticalCat } from "../optical.ts"; 3 | import { monad, type PromiseHkt } from "../promise.ts"; 4 | import { err, ok } from "../result.ts"; 5 | import { assertEquals } from "../../deps.ts"; 6 | 7 | Deno.test("exponential backoff", async () => { 8 | const optic = exponentialBackoff(3, () => Promise.resolve())( 9 | () => ({ bar: 3 } as Record), 10 | )( 11 | (record: Record) => (attempt) => 12 | Promise.resolve( 13 | attempt === 2 ? ok(`OK ${record}`) : err("NOT_FOUND"), 14 | ), 15 | )((data: Record) => (modified: string) => ({ 16 | ...data, 17 | [modified]: modified.length, 18 | })); 19 | const res = await opticalCat(monad)( 20 | { foo: 3 } as Record, 21 | ) 22 | .feed(optic).unwrap(); 23 | assertEquals(res, "OK [object Object]"); 24 | }); 25 | -------------------------------------------------------------------------------- /src/optical/sequential.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @packageDocumentation 3 | * @module 4 | * 5 | * A sequential combinator that merging two computations `C1` and `C2`. It computes next computation twice. 6 | * 7 | * ```text 8 | * +------+ 9 | * S -------->| |---------> A 10 | * | C1 | 11 | * +------| |<--------- B 12 | * | +------+ 13 | * | U 14 | * | +------+ 15 | * +----->| |--------> A 16 | * | C2 | 17 | * T <--------| |<-------- B 18 | * +------+ 19 | * ``` 20 | */ 21 | 22 | import type { Optic } from "../optical.ts"; 23 | 24 | /** 25 | * Creates a sequential combinator that merging two computations `C1` and `C2`. It computes next computation twice. 26 | * 27 | * ```text 28 | * +------+ 29 | * S -------->| |---------> A 30 | * | C1 | 31 | * +------| |<--------- B 32 | * | +------+ 33 | * | U 34 | * | +------+ 35 | * +----->| |--------> A 36 | * | C2 | 37 | * T <--------| |<-------- B 38 | * +------+ 39 | * ``` 40 | */ 41 | export const newSequential = 42 | (computation1: Optic) => 43 | (computation2: Optic): Optic => 44 | (next) => 45 | (received) => 46 | (callback) => 47 | computation1(next)(received)((u) => computation2(next)(u)(callback)); 48 | -------------------------------------------------------------------------------- /src/optical/setter.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "../../deps.ts"; 2 | import { functor, monad } from "../array.ts"; 3 | import { opticCat } from "../optical.ts"; 4 | import { key } from "./lens.ts"; 5 | import { set, setF, setM } from "./setter.ts"; 6 | 7 | Deno.test("double both elements of coord", () => { 8 | const coord = { hoge: 2, foo: [3, 1, 4, 1, 5, 9, 2] as readonly number[] }; 9 | assertEquals( 10 | opticCat(coord) 11 | .feed(key("foo")) 12 | .setWith(setF(functor)((x) => x * 2)), 13 | { hoge: 2, foo: [6, 2, 8, 2, 10, 18, 4] }, 14 | ); 15 | }); 16 | 17 | Deno.test("set", () => { 18 | assertEquals( 19 | opticCat({ foo: 60n, bar: false }) 20 | .feed(key("bar")) 21 | .setWith(set((x) => !x)), 22 | { foo: 60n, bar: true }, 23 | ); 24 | }); 25 | 26 | Deno.test("setM", () => { 27 | const coord = { hoge: 2, foo: [3, 1, 4, 1, 5, 9, 2] as readonly number[] }; 28 | assertEquals( 29 | opticCat(coord) 30 | .feed(key("foo")) 31 | .setWith(setM(monad)((x) => x * 2)), 32 | { hoge: 2, foo: [6, 2, 8, 2, 10, 18, 4] }, 33 | ); 34 | }); 35 | -------------------------------------------------------------------------------- /src/optical/setter.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @packageDocumentation 3 | * @module 4 | * Transformation terminal for a Functor. 5 | * ```text 6 | * S --| never 7 | * [ map ] 8 | * T <-| never 9 | * ``` 10 | */ 11 | 12 | import type { Get1 } from "../hkt.ts"; 13 | import type { Optic } from "../optical.ts"; 14 | import type { Functor } from "../type-class/functor.ts"; 15 | import type { Monad } from "../type-class/monad.ts"; 16 | 17 | /** 18 | * `Setter` is a `Optic` but does not allow to compose any computations more. 19 | */ 20 | export type Setter = Optic; 21 | 22 | /** 23 | * Modifies going data as a terminal. 24 | * 25 | * @param mapper - The function to map the data going. 26 | * @returns The mapping optic like `over`. 27 | */ 28 | export const set = 29 | (mapper: (s: S) => T): Setter => 30 | () => 31 | (received) => 32 | (callback) => callback(mapper(received)); 33 | 34 | /** 35 | * Modifies data contained by `Functor` as a terminal. 36 | * 37 | * @param mapper - The function to map the data going. 38 | * @returns The mapping optic. 39 | */ 40 | export const setF = 41 | (f: Functor) => 42 | (mapper: (s: S) => T): Setter, Get1> => 43 | () => 44 | (received) => 45 | (callback) => callback(f.map(mapper)(received)); 46 | 47 | /** 48 | * Modifies data contained by `Monad` as a terminal. 49 | * 50 | * @param mapper - The function to map the data going. 51 | * @returns The mapping optic. 52 | */ 53 | export const setM = 54 | (m: Monad) => 55 | (mapper: (s: S) => T): Setter, Get1> => 56 | () => 57 | (received) => 58 | (callback) => callback(m.map(mapper)(received)); 59 | -------------------------------------------------------------------------------- /src/optical/traversal.test.ts: -------------------------------------------------------------------------------- 1 | import { overCat } from "../optical.ts"; 2 | import { bitraversable, type Tuple } from "../tuple.ts"; 3 | import { both, traversed } from "./traversal.ts"; 4 | import { assertEquals } from "../../deps.ts"; 5 | import { Array } from "../../mod.ts"; 6 | 7 | Deno.test("get length of string for each element of tuple", () => { 8 | const tuple: Tuple = ["hello", "world"]; 9 | assertEquals( 10 | overCat((s: string) => s.length) 11 | .on(both(bitraversable)) 12 | .from(tuple), 13 | [5, 5], 14 | ); 15 | }); 16 | 17 | Deno.test("traversed", () => { 18 | const values = ["hey", "what", "are", "you", "doing"]; 19 | assertEquals( 20 | overCat((s: string) => s.length) 21 | .on(traversed(Array.traversable)) 22 | .from(values), 23 | [3, 4, 3, 3, 5], 24 | ); 25 | }); 26 | -------------------------------------------------------------------------------- /src/optical/traversal.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @packageDocumentation 3 | * @module 4 | * Traversing combinator for a data structure. 5 | * ```text 6 | * T -----|--[ get ]-> A 7 | * V 8 | * F> <-[ set ]-------- F 9 | * ``` 10 | */ 11 | 12 | import { type Cont, type ContHkt, map, monad } from "../cont.ts"; 13 | import type { Apply2Only, Get1, Get2 } from "../hkt.ts"; 14 | import type { IdentityHkt } from "../identity.ts"; 15 | import * as Identity from "../identity.ts"; 16 | import type { Optic } from "../optical.ts"; 17 | import type { Applicative } from "../type-class/applicative.ts"; 18 | import { 19 | bisequenceA, 20 | type Bitraversable, 21 | } from "../type-class/bitraversable.ts"; 22 | import { sequenceA, type Traversable } from "../type-class/traversable.ts"; 23 | 24 | export type Traversal = Optic< 25 | Get1, 26 | Get1>, 27 | A, 28 | Get1 29 | >; 30 | export type Bitraversal = Optic< 31 | Get2, 32 | Get1>, 33 | A, 34 | Get1 35 | >; 36 | 37 | export const newTraversal = ( 38 | tra: Traversable, 39 | app: Applicative, 40 | ): Traversal => 41 | (next: (sending: A) => Cont>) => 42 | (received: Get1): Cont>> => 43 | map(sequenceA(tra, app))( 44 | tra 45 | .traverse>(monad())(next)(received), 46 | ); 47 | 48 | export const newBitraversal = ( 49 | tra: Bitraversable, 50 | app: Applicative, 51 | ): Bitraversal => 52 | (next: (sending: A) => Cont>) => 53 | (received: Get2): Cont>> => 54 | map(bisequenceA(tra, app))( 55 | tra.bitraverse>(monad())(next)(next)( 56 | received, 57 | ), 58 | ); 59 | 60 | export const traversed = ( 61 | tra: Traversable, 62 | ): Traversal => 63 | newTraversal(tra, Identity.monad); 64 | 65 | export const both = ( 66 | tra: Bitraversable, 67 | ): Bitraversal => 68 | newBitraversal(tra, Identity.monad); 69 | -------------------------------------------------------------------------------- /src/ordering.ts: -------------------------------------------------------------------------------- 1 | import { doT } from "./cat.ts"; 2 | import { 3 | decI8, 4 | type Decoder, 5 | encI8, 6 | type Encoder, 7 | monadForDecoder, 8 | } from "./serial.ts"; 9 | import type { Monoid } from "./type-class/monoid.ts"; 10 | import { semiGroupSymbol } from "./type-class/semi-group.ts"; 11 | 12 | /** 13 | * Means that the left term is less than the right term. 14 | */ 15 | export const less = -1 as const; 16 | export type Less = typeof less; 17 | /** 18 | * Means that the left term equals to the right term. 19 | */ 20 | export const equal = 0 as const; 21 | export type Equal = typeof equal; 22 | /** 23 | * Means that the left term is greater than the right term. 24 | */ 25 | export const greater = 1 as const; 26 | export type Greater = typeof greater; 27 | /** 28 | * The ordering about two terms. 29 | */ 30 | export type Ordering = Less | Equal | Greater; 31 | 32 | export const isLt = (ord: Ordering): ord is Less => ord === less; 33 | export const isGt = (ord: Ordering): ord is Greater => ord === greater; 34 | export const isLe = (ord: Ordering): ord is Less | Equal => ord !== greater; 35 | export const isGe = (ord: Ordering): ord is Greater | Equal => ord !== less; 36 | export const isEq = (ord: Ordering): ord is Equal => ord === equal; 37 | export const isNe = (ord: Ordering): ord is Less | Greater => ord !== equal; 38 | 39 | /** 40 | * The reversed type of `O` extends `Ordering`. 41 | */ 42 | export type Reversed = O extends Less ? Greater 43 | : O extends Greater ? Less 44 | : O extends Equal ? Equal 45 | : Ordering; 46 | /** 47 | * Reverses the order. 48 | * 49 | * @param order - The order value extends `Ordering`. 50 | * @returns The reversed order. 51 | */ 52 | export const reverse = (order: O): Reversed => 53 | (0 - order) as Reversed; 54 | 55 | /** 56 | * Transits two `Ordering`s. Returns `second` if `first` is `equal`, otherwise returns `first`. It is useful to implement `PartialOrd` for some object type. The order of arguments is reversed because of that it is useful for partial applying. 57 | * 58 | * @param second - The second order. 59 | * @param first - The first order. 60 | * @returns The transited order. 61 | */ 62 | export const and = (second: Ordering) => 63 | ( 64 | first: Ordering, 65 | ) => (first === equal ? second : first); 66 | /** 67 | * Transits two `Ordering`s. Returns `secondFn()` if `first` is `equal`, otherwise returns `first`. It is useful to implement `PartialOrd` for some object type. The order of arguments is reversed because of that it is useful for partial applying. 68 | * 69 | * @param secondFn - The function to provide a second order. 70 | * @param first - The first order. 71 | * @returns The transited order. 72 | */ 73 | export const andThen = 74 | (secondFn: () => Ordering) => (first: Ordering): Ordering => 75 | first === equal ? secondFn() : first; 76 | 77 | /** 78 | * The instance of `Monoid` about transitive for `Ordering`. 79 | */ 80 | export const monoid: Monoid = { 81 | combine: (l, r) => and(r)(l), 82 | identity: equal, 83 | [semiGroupSymbol]: true, 84 | }; 85 | 86 | export const enc = (): Encoder => encI8; 87 | export const dec = (): Decoder => 88 | doT(monadForDecoder) 89 | .addM("variant", decI8()) 90 | .finish(({ variant }): Ordering => Math.sign(variant) as Ordering); 91 | -------------------------------------------------------------------------------- /src/predicate.ts: -------------------------------------------------------------------------------- 1 | import type { Hkt1 } from "./hkt.ts"; 2 | import type { Monoid } from "./type-class/monoid.ts"; 3 | import { semiGroupSymbol } from "./type-class/semi-group.ts"; 4 | import type { Contravariant } from "./type-class/variance.ts"; 5 | 6 | /** 7 | * The function from `A` to `boolean`. For any set `A`, the set of `Predicate` will be isomorphic to the set of power set of `A`. 8 | */ 9 | export type Predicate = (a: A) => boolean; 10 | 11 | export interface PredicateHkt extends Hkt1 { 12 | readonly type: Predicate; 13 | } 14 | 15 | /** 16 | * The instance of `Contravariant` for `Predicate`. 17 | */ 18 | export const contra: Contravariant = { 19 | contraMap: (mapper) => (predB) => (a) => predB(mapper(a)), 20 | }; 21 | 22 | /** 23 | * The instance of `Monoid` for `Predicate`. 24 | */ 25 | export const monoid = (): Monoid> => ({ 26 | identity: () => true, 27 | combine: (predL, predR) => (a) => predL(a) && predR(a), 28 | [semiGroupSymbol]: true, 29 | }); 30 | -------------------------------------------------------------------------------- /src/promise.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "../deps.ts"; 2 | import { Cat, Promise, Result } from "../mod.ts"; 3 | 4 | Deno.test("combine services", async () => { 5 | const serviceA = (): Promise> => 6 | Promise.pure(Result.ok("foo")); 7 | const serviceB = ( 8 | x: () => Promise>, 9 | ): Promise> => 10 | Cat.doT(Promise.monadT(Result.traversableMonad())) 11 | .addM("x", x()) 12 | .addM("y", x()) 13 | .finish(({ x, y }) => `Hello, ${x} and ${y}!`); 14 | 15 | const res = await serviceB(serviceA); 16 | assertEquals(res, Result.ok("Hello, foo and foo!")); 17 | }); 18 | -------------------------------------------------------------------------------- /src/promise/monad.ts: -------------------------------------------------------------------------------- 1 | import type { Get1 } from "../hkt.ts"; 2 | import type { Monad } from "../type-class/monad.ts"; 3 | 4 | export type MonadPromise = Monad & { 5 | readonly liftPromise: (a: Promise) => Get1; 6 | }; 7 | -------------------------------------------------------------------------------- /src/range-q.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "../deps.ts"; 2 | import { empty, range } from "./list.ts"; 3 | import { fromList, sum, sumFromStartTo } from "./range-q.ts"; 4 | import { addGroup } from "./type-class/group.ts"; 5 | 6 | Deno.test("empty", () => { 7 | const rangeQ = fromList(addGroup)(empty()); 8 | 9 | assertEquals(sumFromStartTo(-1)(rangeQ), 0); 10 | assertEquals(sumFromStartTo(0)(rangeQ), 0); 11 | assertEquals(sumFromStartTo(1)(rangeQ), 0); 12 | assertEquals(sumFromStartTo(2)(rangeQ), 0); 13 | }); 14 | 15 | Deno.test("sum range", () => { 16 | const rangeQ = fromList(addGroup)(range(0, 6)); 17 | 18 | assertEquals(sumFromStartTo(0)(rangeQ), 0); 19 | assertEquals(sumFromStartTo(1)(rangeQ), 0); 20 | assertEquals(sumFromStartTo(2)(rangeQ), 1); 21 | assertEquals(sumFromStartTo(3)(rangeQ), 3); 22 | assertEquals(sumFromStartTo(4)(rangeQ), 6); 23 | assertEquals(sumFromStartTo(5)(rangeQ), 10); 24 | assertEquals(sumFromStartTo(6)(rangeQ), 15); 25 | assertEquals(sumFromStartTo(7)(rangeQ), 15); 26 | 27 | for (let i = 0; i < 6; ++i) { 28 | for (let j = 0; j < 6; ++j) { 29 | let expected = 0; 30 | for (let x = i; x < j; ++x) { 31 | expected += x; 32 | } 33 | assertEquals(sum(i)(j)(rangeQ), expected); 34 | } 35 | } 36 | }); 37 | -------------------------------------------------------------------------------- /src/range-q.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This module provides a helper data structure that can find the group sum for a range in constant time. 3 | * 4 | * @packageDocumentation 5 | * @module 6 | */ 7 | 8 | import { type List, toIterator } from "./list.ts"; 9 | import type { Group } from "./type-class/group.ts"; 10 | 11 | /** 12 | * A helper data structure that can find the group sum for a range in constant time. 13 | */ 14 | export type RangeQ = Readonly<{ 15 | /** 16 | * The accumulating calculated items. 17 | */ 18 | acc: readonly T[]; 19 | /** 20 | * The `Group` instance for `T`. 21 | */ 22 | group: Group; 23 | }>; 24 | 25 | /** 26 | * Creates a new `RangeQ` from the `Group` instance and iterable sequence. 27 | * 28 | * @param group - A `Group` instance for `T`. 29 | * @param iterable - Source sequence of `T`. 30 | * @returns The new `RangeQ`. 31 | */ 32 | export const fromIterable = 33 | (group: Group) => (iterable: Iterable): RangeQ => ({ 34 | acc: [...iterable].reduce<[T[], T]>( 35 | (prev, curr) => { 36 | const next = group.combine(prev[1], curr); 37 | return [[...prev[0], next], next] as [T[], T]; 38 | }, 39 | [[], group.identity], 40 | )[0], 41 | group, 42 | }); 43 | 44 | /** 45 | * Creates a new `RangeQ` from the `Group` instance and lazy list. 46 | * 47 | * @param group - A `Group` instance for `T`. 48 | * @param list - Source lazy list of `T`. 49 | * @returns The new `RangeQ`. 50 | */ 51 | export const fromList = (group: Group) => (list: List): RangeQ => 52 | fromIterable(group)(toIterator(list)); 53 | 54 | /** 55 | * Gets the length of items from the `RangeQ`. 56 | * 57 | * @param range - To be queried. 58 | * @returns The number of items. 59 | */ 60 | export const len = (range: RangeQ): number => range.acc.length; 61 | 62 | /** 63 | * Checks whether there are no items in the `RangeQ`. 64 | * 65 | * @param range - To be queried. 66 | * @returns Whether there are no items. 67 | */ 68 | export const isEmpty = (range: RangeQ): boolean => range.acc.length === 0; 69 | 70 | /** 71 | * Sums up the elements for a range between `0` (inclusive) and `end` (exclusive). It takes only constant time (`O(1)`). 72 | * 73 | * @param end - The end index (exclusive). 74 | * @param range - To be queried. 75 | * @returns The group sum for the range. 76 | */ 77 | export const sumFromStartTo = (end: number) => (range: RangeQ): T => 78 | (end <= 0 || range.acc.length === 0) 79 | ? range.group.identity 80 | : end >= range.acc.length 81 | ? range.acc[range.acc.length - 1] 82 | : range.acc[end - 1]; 83 | 84 | /** 85 | * Sums up the elements for a range between `start` (inclusive) and `end` (exclusive). It takes only constant time (`O(1)`). 86 | * 87 | * @param start - The start index (inclusive). 88 | * @param end - The end index (exclusive). 89 | * @param range - To be queried. 90 | * @returns The group sum for the range. 91 | */ 92 | export const sum = 93 | (start: number) => (end: number) => (range: RangeQ): T => 94 | start >= end ? range.group.identity : range.group.combine( 95 | range.group.invert(sumFromStartTo(start)(range)), 96 | sumFromStartTo(end)(range), 97 | ); 98 | -------------------------------------------------------------------------------- /src/reader.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "../deps.ts"; 2 | import { catT } from "./cat.ts"; 3 | import { mapOr, none, type Option, some } from "./option.ts"; 4 | import { local } from "./reader.ts"; 5 | import { run } from "./reader.ts"; 6 | import { ask } from "./reader.ts"; 7 | import { monad } from "./reader.ts"; 8 | import type { Reader } from "./reader.ts"; 9 | 10 | Deno.test("ask", () => { 11 | interface User { 12 | name: string; 13 | } 14 | const userCat = catT(monad()); 15 | 16 | const message = (): Reader => 17 | userCat(ask()).finish( 18 | ({ name }) => `Hello, ${name}!`, 19 | ); 20 | const box = (): Reader => 21 | userCat(message()).finish( 22 | (mes) => `
${mes}
`, 23 | ); 24 | 25 | assertEquals( 26 | run(box())({ name: "John" }), 27 | '
Hello, John!
', 28 | ); 29 | assertEquals( 30 | run(box())({ name: "Alice" }), 31 | '
Hello, Alice!
', 32 | ); 33 | }); 34 | 35 | Deno.test("local", () => { 36 | interface User { 37 | name: string; 38 | id: string; 39 | score: number; 40 | } 41 | interface Bulk { 42 | users: readonly User[]; 43 | } 44 | 45 | const extractFromBulk = (id: string) => 46 | local((bulk: Bulk): Option => { 47 | const found = bulk.users.find((elem) => elem.id === id); 48 | if (!found) { 49 | return none(); 50 | } 51 | return some(found); 52 | }); 53 | const scoreReport = (id: string): Reader => 54 | extractFromBulk(id)( 55 | catT(monad>())(ask>()) 56 | .finish( 57 | mapOr("user not found")(({ name, score }) => 58 | `${name}'s score is ${score}!` 59 | ), 60 | ), 61 | ); 62 | 63 | const bulk: Bulk = { 64 | users: [ 65 | { name: "John", id: "1321", score: 12130 }, 66 | { name: "Alice", id: "4209", score: 320123 }, 67 | ], 68 | }; 69 | assertEquals(run(scoreReport("1321"))(bulk), "John's score is 12130!"); 70 | assertEquals(run(scoreReport("4209"))(bulk), "Alice's score is 320123!"); 71 | }); 72 | -------------------------------------------------------------------------------- /src/reader/monad.ts: -------------------------------------------------------------------------------- 1 | import type { Get1 } from "../hkt.ts"; 2 | import type { Monad } from "../type-class/monad.ts"; 3 | 4 | export type MonadReader = Monad & { 5 | readonly ask: () => Get1; 6 | readonly local: ( 7 | modifier: (record: R) => R, 8 | ) =>
(m: Get1) => Get1; 9 | }; 10 | 11 | export const reader = 12 | (mr: MonadReader) => 13 | (selector: (record: R) => A): Get1 => mr.map(selector)(mr.ask()); 14 | -------------------------------------------------------------------------------- /src/reverse.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "../deps.ts"; 2 | import * as Number from "./number.ts"; 3 | import { some } from "./option.ts"; 4 | import { equal, greater, less, type Ordering } from "./ordering.ts"; 5 | import { partialOrd, pure } from "./reverse.ts"; 6 | 7 | Deno.test("order", () => { 8 | const order = partialOrd(Number.partialOrd); 9 | assertEquals(order.eq(pure(2), pure(2)), true); 10 | assertEquals(order.eq(pure(1), pure(2)), false); 11 | assertEquals(order.partialCmp(pure(1), pure(2)), some(greater as Ordering)); 12 | assertEquals(order.partialCmp(pure(2), pure(1)), some(less as Ordering)); 13 | assertEquals(order.partialCmp(pure(2), pure(2)), some(equal as Ordering)); 14 | }); 15 | -------------------------------------------------------------------------------- /src/reverse.ts: -------------------------------------------------------------------------------- 1 | import type { Hkt1 } from "./hkt.ts"; 2 | import { map as optionMap } from "./option.ts"; 3 | import { reverse } from "./ordering.ts"; 4 | import { eqSymbol } from "./type-class/eq.ts"; 5 | import type { Monad } from "./type-class/monad.ts"; 6 | import type { Ord } from "./type-class/ord.ts"; 7 | import type { PartialOrd } from "./type-class/partial-ord.ts"; 8 | import type { Traversable } from "./type-class/traversable.ts"; 9 | 10 | const revSymbol = Symbol("ReverseItem"); 11 | 12 | /** 13 | * An utility container to reverse the order of value. 14 | */ 15 | export type Reverse = { 16 | [revSymbol]: T; 17 | }; 18 | 19 | export const partialOrd = ( 20 | order: PartialOrd, 21 | ): PartialOrd> => ({ 22 | partialCmp: (lhs, rhs) => 23 | optionMap(reverse)(order.partialCmp(lhs[revSymbol], rhs[revSymbol])), 24 | eq: (lhs, rhs) => order.eq(lhs[revSymbol], rhs[revSymbol]), 25 | }); 26 | export const ord = (order: Ord): Ord> => ({ 27 | ...partialOrd(order), 28 | cmp: (lhs, rhs) => order.cmp(lhs[revSymbol], rhs[revSymbol]), 29 | [eqSymbol]: true, 30 | }); 31 | 32 | export const pure = (item: T): Reverse => ({ [revSymbol]: item }); 33 | 34 | export const map = (fn: (t: T) => U) => (r: Reverse): Reverse => ({ 35 | [revSymbol]: fn(r[revSymbol]), 36 | }); 37 | 38 | export const flatMap = 39 | (fn: (t: T) => Reverse) => (r: Reverse): Reverse => 40 | fn(r[revSymbol]); 41 | 42 | export const apply = 43 | (fn: Reverse<(t: T) => U>) => (r: Reverse): Reverse => ({ 44 | [revSymbol]: fn[revSymbol](r[revSymbol]), 45 | }); 46 | 47 | export interface ReverseHkt extends Hkt1 { 48 | readonly type: Reverse; 49 | } 50 | 51 | export const monad: Monad = { 52 | pure, 53 | map, 54 | flatMap, 55 | apply, 56 | }; 57 | 58 | export const traversable: Traversable = { 59 | map, 60 | foldR: (folder) => (init) => (rev) => folder(rev[revSymbol])(init), 61 | traverse: (app) => (visitor) => (rev) => 62 | app.map(pure)(visitor(rev[revSymbol])), 63 | }; 64 | -------------------------------------------------------------------------------- /src/seg-tree.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "../deps.ts"; 2 | import { doMut, readMutRef } from "./mut.ts"; 3 | import { 4 | clear, 5 | get, 6 | insert, 7 | intoItems, 8 | intoMut, 9 | query, 10 | withItems, 11 | } from "./seg-tree.ts"; 12 | import { addMonoid } from "./type-class/monoid.ts"; 13 | 14 | Deno.test("query", () => { 15 | const src = [1, 4, 2, 3, 5, 2, 3]; 16 | const seq = withItems(addMonoid)(src); 17 | for (let from = -1; from <= 7; ++from) { 18 | for (let to = 0; to <= 8; ++to) { 19 | let expected = 0; 20 | for (let i = from; i < to; ++i) { 21 | expected += i in src ? src[i] : 0; 22 | } 23 | assertEquals(query(from)(to)(seq), expected); 24 | } 25 | } 26 | }); 27 | 28 | Deno.test("get", () => { 29 | const src = [1, 4, 2, 3, 5, 2, 3]; 30 | const seq = withItems(addMonoid)(src); 31 | for (let i = 0; i < src.length; ++i) { 32 | assertEquals(get(i)(seq), src[i]); 33 | } 34 | }); 35 | 36 | Deno.test("intoItems", () => { 37 | const src = [1, 4, 2, 3, 5, 2, 3]; 38 | const seq = withItems(addMonoid)(src); 39 | const actual = intoItems(seq); 40 | assertEquals(actual.length, src.length); 41 | for (let i = 0; i < src.length; ++i) { 42 | assertEquals(actual[i], src[i]); 43 | } 44 | }); 45 | 46 | Deno.test("clear", () => { 47 | const src = [1, 4, 2, 3, 5, 2, 3]; 48 | const seq = withItems(addMonoid)(src); 49 | const cleared = doMut((cat) => 50 | cat.addM("ref", intoMut(seq)) 51 | .runWith(({ ref }) => clear(ref)) 52 | .finishM(({ ref }) => readMutRef(ref)) 53 | ); 54 | const actual = intoItems(cleared); 55 | assertEquals(actual.length, src.length); 56 | for (let i = 0; i < src.length; ++i) { 57 | assertEquals(actual[i], 0); 58 | } 59 | }); 60 | 61 | Deno.test("insert", () => { 62 | const src = [1, 4, 2, 3, 5, 2, 3]; 63 | const seq = withItems(addMonoid)(src); 64 | assertEquals(query(1)(3)(seq), 6); 65 | const inserted = doMut((cat) => 66 | cat.addM("ref", intoMut(seq)) 67 | .runWith(({ ref }) => insert(ref)(1)(7)) 68 | .finishM(({ ref }) => readMutRef(ref)) 69 | ); 70 | assertEquals(query(1)(3)(inserted), 9); 71 | }); 72 | -------------------------------------------------------------------------------- /src/seq/finger-tree.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "../../deps.ts"; 2 | import { Array } from "../../mod.ts"; 3 | import { cat } from "../cat.ts"; 4 | import { 5 | appendBetween, 6 | appendManyToHead, 7 | appendToHead, 8 | concat, 9 | empty, 10 | fromArray, 11 | isDeep, 12 | isEmpty, 13 | isSingle, 14 | type Node, 15 | reduceNode, 16 | reduceTree, 17 | size, 18 | } from "./finger-tree.ts"; 19 | 20 | const toArray = Array.fromReduce(reduceTree); 21 | 22 | Deno.test("type check", () => { 23 | const emptiness = empty; 24 | const single = fromArray([3]); 25 | const many = fromArray([2, 1, 8, 1, 8]); 26 | 27 | assertEquals(isEmpty(emptiness), true); 28 | assertEquals(isEmpty(single), false); 29 | assertEquals(isEmpty(many), false); 30 | 31 | assertEquals(isSingle(emptiness), false); 32 | assertEquals(isSingle(single), true); 33 | assertEquals(isSingle(many), false); 34 | 35 | assertEquals(isDeep(emptiness), false); 36 | assertEquals(isDeep(single), false); 37 | assertEquals(isDeep(many), true); 38 | }); 39 | 40 | Deno.test("reduceNode", () => { 41 | const small: Node = ["a", "b"]; 42 | const big: Node = ["x", "y", "z"]; 43 | 44 | const concatStr = (x: string) => (y: string) => x + y; 45 | assertEquals(reduceNode.reduceR(concatStr)(small)("!"), "ab!"); 46 | assertEquals(reduceNode.reduceR(concatStr)(big)("!"), "xyz!"); 47 | }); 48 | 49 | Deno.test("size", () => { 50 | const emptiness = empty; 51 | const single = fromArray([3]); 52 | const many = fromArray([2, 1, 8, 1, 8]); 53 | 54 | assertEquals(size(emptiness), 0); 55 | assertEquals(size(single), 1); 56 | assertEquals(size(many), 5); 57 | }); 58 | 59 | Deno.test("appendToHead", () => { 60 | const actual = cat(empty) 61 | .feed(appendToHead("?")) 62 | .feed(appendToHead("i")) 63 | .feed(appendToHead("h")) 64 | .value; 65 | 66 | assertEquals(toArray(actual), ["h", "i", "?"]); 67 | }); 68 | 69 | Deno.test("appendManyToHead", () => { 70 | const actual = appendManyToHead(Array.reduce)("Vinegar".split(""))(empty); 71 | 72 | assertEquals(toArray(actual), ["V", "i", "n", "e", "g", "a", "r"]); 73 | }); 74 | 75 | Deno.test("appendBetween", () => { 76 | assertEquals( 77 | toArray(appendBetween(empty)(["d", "o", "t"])(empty)), 78 | ["d", "o", "t"], 79 | ); 80 | assertEquals( 81 | toArray( 82 | appendBetween(fromArray(["g", "o"]))(["d", "o", "t"])( 83 | empty, 84 | ), 85 | ), 86 | ["g", "o", "d", "o", "t"], 87 | ); 88 | assertEquals( 89 | toArray( 90 | appendBetween(empty)(["d", "o", "t"])( 91 | fromArray(["t", "e", "r"]), 92 | ), 93 | ), 94 | ["d", "o", "t", "t", "e", "r"], 95 | ); 96 | assertEquals( 97 | toArray( 98 | appendBetween(fromArray(["g", "o"]))(["d", "o", "t"])( 99 | fromArray(["t", "e", "r"]), 100 | ), 101 | ), 102 | ["g", "o", "d", "o", "t", "t", "e", "r"], 103 | ); 104 | }); 105 | 106 | Deno.test("concat", () => { 107 | const emptiness = empty; 108 | const single = fromArray([3]); 109 | const many = fromArray([2, 1, 8, 2, 8]); 110 | 111 | assertEquals(toArray(concat(emptiness)(emptiness)), []); 112 | assertEquals(toArray(concat(emptiness)(single)), [3]); 113 | assertEquals(toArray(concat(single)(emptiness)), [3]); 114 | assertEquals(toArray(concat(single)(single)), [3, 3]); 115 | assertEquals(toArray(concat(emptiness)(many)), [2, 1, 8, 2, 8]); 116 | assertEquals(toArray(concat(many)(emptiness)), [2, 1, 8, 2, 8]); 117 | assertEquals(toArray(concat(single)(many)), [3, 2, 1, 8, 2, 8]); 118 | assertEquals(toArray(concat(many)(single)), [2, 1, 8, 2, 8, 3]); 119 | assertEquals(toArray(concat(many)(many)), [ 120 | 2, 121 | 1, 122 | 8, 123 | 2, 124 | 8, 125 | 2, 126 | 1, 127 | 8, 128 | 2, 129 | 8, 130 | ]); 131 | }); 132 | -------------------------------------------------------------------------------- /src/serial.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "../deps.ts"; 2 | import { doT } from "./cat.ts"; 3 | import { encU16Be, encU32Be, encU8, monadForCodeM, runCode } from "./serial.ts"; 4 | 5 | Deno.test("single integer", async () => { 6 | const code = encU8(42); 7 | const buf = await runCode(code); 8 | assertEquals(new Uint8Array(buf)[0], 42); 9 | }); 10 | 11 | Deno.test("multiple integers", async () => { 12 | const code = doT(monadForCodeM) 13 | .run(encU8(3)) 14 | .run(encU16Be(1)) 15 | .run(encU32Be(4)) 16 | .finish(() => []); 17 | const buf = await runCode(code); 18 | const array = new Uint8Array(buf); 19 | assertEquals(array[0], 3); 20 | assertEquals(array[1], 0); 21 | assertEquals(array[2], 1); 22 | assertEquals(array[3], 0); 23 | assertEquals(array[4], 0); 24 | assertEquals(array[5], 0); 25 | assertEquals(array[6], 4); 26 | }); 27 | -------------------------------------------------------------------------------- /src/star.ts: -------------------------------------------------------------------------------- 1 | import type { Apply3Only, Get1, Get2, Hkt3 } from "./hkt.ts"; 2 | import type { Functor } from "./type-class/functor.ts"; 3 | import { type Profunctor, rightMap } from "./type-class/profunctor.ts"; 4 | 5 | export type Star = (d: D) => Get1; 6 | 7 | export interface StarHkt extends Hkt3 { 8 | readonly type: Star; 9 | } 10 | 11 | export const pro = ( 12 | functor: Functor, 13 | ): Profunctor> => ({ 14 | diMap: (f) => (g) => (m) => (d) => functor.map(g)(m(f(d))), 15 | }); 16 | 17 | export const map = ( 18 | functor: Functor, 19 | ): ( 20 | fn: (t: T) => U, 21 | ) => ( 22 | item: Get2, A, T>, 23 | ) => Get2, A, U> => rightMap(pro(functor)); 24 | -------------------------------------------------------------------------------- /src/state.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "../deps.ts"; 2 | import { cat } from "./cat.ts"; 3 | import type { Apply2Only } from "./hkt.ts"; 4 | import { get, monad, put, type State, type StateHkt } from "./state.ts"; 5 | import { begin, bindT } from "./type-class/monad.ts"; 6 | 7 | Deno.test("roll three dices", () => { 8 | const seed = 1423523; 9 | const xorShiftRng = 10 | (): State => (state: number): [number, number] => { 11 | state ^= state << 13; 12 | state ^= state >> 17; 13 | state ^= state << 5; 14 | return [state, state]; 15 | }; 16 | 17 | const bound = bindT>(monad())(xorShiftRng); 18 | const results = cat(begin(monad())) 19 | .feed(bound("result1")) 20 | .feed(bound("result2")) 21 | .feed(bound("result3")) 22 | .value(seed)[0]; 23 | assertEquals(results, { 24 | result1: 1463707459, 25 | result2: -519004248, 26 | result3: -1370047078, 27 | }); 28 | }); 29 | 30 | Deno.test("twenty times", () => { 31 | const stateMonad = monad(); 32 | 33 | const twentyTimes = (x: number): number => 34 | cat(put(x + x)) 35 | .feed(stateMonad.flatMap(get)) 36 | .feed(stateMonad.map((x2: number) => x2 + x2)) 37 | .feed(stateMonad.map((x4: number) => x4 + x4)) 38 | .feed( 39 | stateMonad.flatMap((x8: number) => 40 | stateMonad.map((x2: number) => x8 + x2)( 41 | get(), 42 | ) 43 | ), 44 | ) 45 | .feed(stateMonad.map((x10: number) => x10 + x10)) 46 | .value(0)[0]; 47 | 48 | assertEquals(twentyTimes(10), 200); 49 | }); 50 | -------------------------------------------------------------------------------- /src/state/monad.ts: -------------------------------------------------------------------------------- 1 | import type { Get1 } from "../hkt.ts"; 2 | import type { Monad } from "../type-class/monad.ts"; 3 | 4 | export type MonadState = Monad & { 5 | readonly state: (modifier: (state: S) => [A, S]) => Get1; 6 | }; 7 | 8 | export const get = (s: MonadState): Get1 => 9 | s.state((state) => [state, state]); 10 | export const set = 11 | (s: MonadState) => (state: S): Get1 => 12 | s.state(() => [[], state]); 13 | 14 | export const modify = 15 | (s: MonadState) => 16 | (modifier: (state: S) => S): Get1 => 17 | s.state((state) => [[], modifier(state)]); 18 | export const gets = 19 | (s: MonadState) => (f: (state: S) => A): Get1 => 20 | s.map(f)(get(s)); 21 | -------------------------------------------------------------------------------- /src/store.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "../deps.ts"; 2 | import { constant } from "./func.ts"; 3 | import { 4 | filter, 5 | fromArray, 6 | functor as listFunctor, 7 | length, 8 | type List, 9 | range, 10 | toArray, 11 | toIterator, 12 | zip, 13 | } from "./list.ts"; 14 | import { experiment, extend, extract, type Store } from "./store.ts"; 15 | 16 | Deno.test("store with life game", () => { 17 | type Coord = [number, number]; 18 | type CellPlane = Store; 19 | type Conway = "Dead" | "Alive"; 20 | type Area = { width: number; height: number }; 21 | 22 | const neighborsOf = ([x, y]: Coord): List => 23 | fromArray( 24 | [-1, 0, 1] 25 | .flatMap((dx) => [-1, 0, 1].map((dy) => [dx, dy])) 26 | .filter(([px, py]) => !(px === 0 && py === 0)) 27 | .map(([dx, dy]) => [x + dx, y + dy]), 28 | ); 29 | const neighborCells: (plane: CellPlane) => List = 30 | experiment(listFunctor)(neighborsOf); 31 | const step = (plane: CellPlane): Conway => { 32 | const cell = extract(plane); 33 | const neighborCount = length( 34 | filter((c: Conway) => c === "Alive")(neighborCells(plane)), 35 | ); 36 | if (cell === "Dead" && neighborCount === 3) { 37 | return "Alive"; 38 | } 39 | if (cell === "Alive" && (neighborCount === 2 || neighborCount === 3)) { 40 | return "Alive"; 41 | } 42 | return "Dead"; 43 | }; 44 | const evolve = extend(step); 45 | 46 | const toCellPlane = (record: Conway[][]): CellPlane => ({ 47 | index: [0, 0], 48 | accessor: ([x, y]) => record?.[y]?.[x] ?? "Dead", 49 | }); 50 | const fromCellPlane = 51 | (plane: CellPlane) => ({ width, height }: Area): Conway[][] => { 52 | const xs = toArray(range(0, width)); 53 | const ys = toArray(range(0, height)); 54 | const coords = fromArray( 55 | ys.flatMap((y) => xs.map((x): Coord => [x, y])), 56 | ); 57 | const array: Conway[][] = []; 58 | for ( 59 | const [[x, y], cell] of toIterator( 60 | zip(coords)( 61 | experiment(listFunctor)(constant(coords))(plane), 62 | ), 63 | ) 64 | ) { 65 | if (!array[y]) { 66 | array[y] = []; 67 | } 68 | array[y][x] = cell; 69 | } 70 | return array; 71 | }; 72 | 73 | /* 74 | | O | 75 | | O | 76 | |OOO | 77 | | | 78 | */ 79 | const mapping: Conway[][] = [ 80 | ["Dead", "Alive", "Dead", "Dead"], 81 | ["Dead", "Dead", "Alive", "Dead"], 82 | ["Alive", "Alive", "Alive", "Dead"], 83 | ["Dead", "Dead", "Dead", "Dead"], 84 | ]; 85 | const initialPlane = toCellPlane(mapping); 86 | const evolved = evolve(initialPlane); 87 | /* 88 | | | 89 | |O O | 90 | | OO | 91 | | O | 92 | */ 93 | const nextPlane = fromCellPlane(evolved)({ width: 4, height: 4 }); 94 | assertEquals(nextPlane, [ 95 | ["Dead", "Dead", "Dead", "Dead"], 96 | ["Alive", "Dead", "Alive", "Dead"], 97 | ["Dead", "Alive", "Alive", "Dead"], 98 | ["Dead", "Alive", "Dead", "Dead"], 99 | ]); 100 | }); 101 | -------------------------------------------------------------------------------- /src/store/comonad.ts: -------------------------------------------------------------------------------- 1 | import type { Get1 } from "../hkt.ts"; 2 | import type { Comonad } from "../type-class/comonad.ts"; 3 | import type { Functor } from "../type-class/functor.ts"; 4 | 5 | export type ComonadStore = Comonad & { 6 | readonly pos: (store: Get1) => S; 7 | readonly peek: (position: S) => (store: Get1) => A; 8 | }; 9 | 10 | export const peeks = 11 | (cs: ComonadStore) => 12 | (modifier: (position: S) => S) => 13 | (store: Get1): A => cs.peek(modifier(cs.pos(store)))(store); 14 | 15 | export const seek = 16 | (cs: ComonadStore) => 17 | (position: S) => 18 | (store: Get1): Get1 => 19 | cs.peek(position)(cs.duplicate(store)); 20 | 21 | export const seeks = 22 | (cs: ComonadStore) => 23 | (modifier: (position: S) => S) => 24 | (store: Get1): Get1 => 25 | peeks(cs)(modifier)(cs.duplicate(store)); 26 | 27 | export const experiment = 28 | (cs: ComonadStore, functor: Functor) => 29 | (modifier: (position: S) => Get1) => 30 | (store: Get1): Get1 => 31 | functor.map((position: S) => cs.peek(position)(store))( 32 | modifier(cs.pos(store)), 33 | ); 34 | -------------------------------------------------------------------------------- /src/string.ts: -------------------------------------------------------------------------------- 1 | import { equal, greater, less, type Ordering } from "./ordering.ts"; 2 | import type { Monoid } from "./type-class/monoid.ts"; 3 | import { fromCmp, type Ord } from "./type-class/ord.ts"; 4 | import { type SemiGroup, semiGroupSymbol } from "./type-class/semi-group.ts"; 5 | 6 | export const cmp = (lhs: string, rhs: string): Ordering => { 7 | if (lhs === rhs) { 8 | return equal; 9 | } 10 | if (lhs < rhs) { 11 | return less; 12 | } 13 | return greater; 14 | }; 15 | export const ord: Ord = fromCmp(() => cmp)(); 16 | 17 | /** 18 | * A `SemiGroup` instance of concatenating `string`s. 19 | */ 20 | export const semiGroup: SemiGroup = { 21 | combine: (l, r) => l + r, 22 | [semiGroupSymbol]: true, 23 | }; 24 | 25 | /** 26 | * A `Monoid` instance of concatenating `string`s. 27 | */ 28 | export const monoid: Monoid = { 29 | ...semiGroup, 30 | identity: "", 31 | }; 32 | -------------------------------------------------------------------------------- /src/tagged.ts: -------------------------------------------------------------------------------- 1 | import type { Hkt2 } from "./hkt.ts"; 2 | 3 | export type Tagged<_S, B> = { 4 | readonly value: B; 5 | }; 6 | 7 | export const tagged = (value: B): Tagged => ({ value }); 8 | 9 | export const unTagged = (t: Tagged) => t.value; 10 | 11 | export interface TaggedHkt extends Hkt2 { 12 | readonly type: Tagged; 13 | } 14 | -------------------------------------------------------------------------------- /src/trans.ts: -------------------------------------------------------------------------------- 1 | import type { Get1, Get2, Hkt1, Hkt2 } from "./hkt.ts"; 2 | import type { Monad } from "./type-class/monad.ts"; 3 | 4 | export interface MonadTransHkt extends Hkt2 { 5 | readonly arg2: Hkt1; 6 | } 7 | 8 | export type MonadTrans = { 9 | readonly lift: (monad: Monad) => (ma: Get1) => Get2; 10 | }; 11 | -------------------------------------------------------------------------------- /src/tropical.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals, assertThrows } from "../deps.ts"; 2 | import { none, type Option, some } from "./option.ts"; 3 | import { fromNumber, fromNumberChecked, semiRing } from "./tropical.ts"; 4 | 5 | Deno.test("fromNumber", () => { 6 | assertEquals(fromNumber(-42), -42); 7 | assertEquals(fromNumber(-0), -0); 8 | assertEquals(fromNumber(0), 0); 9 | assertEquals(fromNumber(42), 42); 10 | assertEquals(fromNumber(Infinity), Infinity); 11 | assertThrows(() => fromNumber(-Infinity)); 12 | assertThrows(() => fromNumber(NaN)); 13 | }); 14 | 15 | Deno.test("fromNumberChecked", () => { 16 | assertEquals(fromNumberChecked(-42) as Option, some(-42)); 17 | assertEquals(fromNumberChecked(-0) as Option, some(-0)); 18 | assertEquals(fromNumberChecked(0) as Option, some(0)); 19 | assertEquals(fromNumberChecked(42) as Option, some(42)); 20 | assertEquals(fromNumberChecked(Infinity) as Option, some(Infinity)); 21 | assertEquals(fromNumberChecked(-Infinity), none()); 22 | assertEquals(fromNumberChecked(NaN), none()); 23 | }); 24 | 25 | Deno.test("semi ring laws", () => { 26 | const { additive, multiplication } = semiRing; 27 | 28 | const x = fromNumber(-42); 29 | const y = fromNumber(0); 30 | const z = fromNumber(42); 31 | 32 | // additive associative 33 | assertEquals( 34 | additive.combine(x, additive.combine(y, z)), 35 | additive.combine(additive.combine(x, y), z), 36 | ); 37 | 38 | // additive identity 39 | for (const v of [x, y, z]) { 40 | assertEquals(additive.combine(v, additive.identity), v); 41 | assertEquals(additive.combine(additive.identity, v), v); 42 | } 43 | 44 | // multiplication associative 45 | assertEquals( 46 | multiplication.combine(x, multiplication.combine(y, z)), 47 | multiplication.combine(multiplication.combine(x, y), z), 48 | ); 49 | 50 | // multiplication identity 51 | for (const v of [x, y, z]) { 52 | assertEquals(multiplication.combine(v, multiplication.identity), v); 53 | assertEquals(multiplication.combine(multiplication.identity, v), v); 54 | } 55 | 56 | // zero * x = x * zero = zero 57 | for (const v of [x, y, z]) { 58 | assertEquals( 59 | multiplication.combine(additive.identity, v), 60 | additive.identity, 61 | ); 62 | assertEquals( 63 | multiplication.combine(v, additive.identity), 64 | additive.identity, 65 | ); 66 | } 67 | }); 68 | -------------------------------------------------------------------------------- /src/tropical.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This module provides a structure that denotes tropical semi-ring. 3 | * 4 | * Additive and multiplication of `Tropical` variable x and y is defined as: 5 | * 6 | * - `add(x, y)` := `Math.min(x, y)`, 7 | * - `mul(x, y)` := `x + y`. 8 | * 9 | * This module exports `semiRing` which is about this mathematical structure. 10 | * 11 | * @packageDocumentation 12 | * @module 13 | */ 14 | 15 | import { none, type Option, some } from "./option.ts"; 16 | import { abelSymbol } from "./type-class/abelian-group.ts"; 17 | import type { AbelianMonoid } from "./type-class/abelian-monoid.ts"; 18 | import type { Monoid } from "./type-class/monoid.ts"; 19 | import { semiGroupSymbol } from "./type-class/semi-group.ts"; 20 | import type { SemiRing } from "./type-class/semi-ring.ts"; 21 | 22 | declare const tropicalNominal: unique symbol; 23 | /** 24 | * A tropical semi-ring data which consists of finite numbers and positive infinity. 25 | */ 26 | export type Tropical = number & { [tropicalNominal]: never }; 27 | 28 | /** 29 | * Transforms a number into a `Tropical`. 30 | * 31 | * @param num - Source integer. 32 | * @returns The new number of `Tropical`. 33 | * 34 | * # Throws 35 | * 36 | * It throws an error only if `num` is negative infinity or NaN. 37 | */ 38 | export const fromNumber = (num: number): Tropical => { 39 | if (Number.NEGATIVE_INFINITY < num) { 40 | return num as Tropical; 41 | } 42 | throw new Error("tropical num must be finite or positive infinity"); 43 | }; 44 | 45 | /** 46 | * Checks and transforms a number into a `Tropical`. 47 | * 48 | * @param num - Source integer. 49 | * @returns The new number of `Tropical`, or none if failed. 50 | */ 51 | export const fromNumberChecked = (num: number): Option => { 52 | if (Number.NEGATIVE_INFINITY < num) { 53 | return some(num as Tropical); 54 | } 55 | return none(); 56 | }; 57 | 58 | /** 59 | * Additive about `Math.min` on `Tropical`. 60 | */ 61 | export const additive: AbelianMonoid = { 62 | identity: Number.POSITIVE_INFINITY as Tropical, 63 | combine: (l, r) => Math.min(l, r) as Tropical, 64 | [abelSymbol]: true, 65 | [semiGroupSymbol]: true, 66 | }; 67 | 68 | /** 69 | * Multiplication about `+` on `Tropical`. 70 | */ 71 | export const multiplication: Monoid = { 72 | identity: 0 as Tropical, 73 | combine: (l, r) => (l + r) as Tropical, 74 | [semiGroupSymbol]: true, 75 | }; 76 | 77 | /** 78 | * Semi-ring about `Math.min` as additive and `+` as multiplication on `Tropical`. 79 | */ 80 | export const semiRing: SemiRing = { additive, multiplication }; 81 | -------------------------------------------------------------------------------- /src/tuple-n.ts: -------------------------------------------------------------------------------- 1 | import { isNone, none, type Option, some } from "./option.ts"; 2 | import { and, equal, type Ordering } from "./ordering.ts"; 3 | import { fromCmp, type Ord } from "./type-class/ord.ts"; 4 | import { fromPartialCmp, type PartialOrd } from "./type-class/partial-ord.ts"; 5 | 6 | export type TupleN = { 7 | readonly [K in keyof T]: PartialOrd; 8 | }; 9 | 10 | export const partialCmp = ( 11 | orderDict: { 12 | readonly [K in keyof T]: PartialOrd; 13 | }, 14 | ) => 15 | (lhs: TupleN, rhs: TupleN): Option => { 16 | const len = Math.min(lhs.length, rhs.length); 17 | let result: Ordering = equal; 18 | for (let i = 0; i < len; i += 1) { 19 | const order = orderDict[i].partialCmp(lhs[i], rhs[i]); 20 | if (isNone(order)) { 21 | return none(); 22 | } 23 | result = and(order[1])(result); 24 | } 25 | return some(result); 26 | }; 27 | export const partialOrd: ( 28 | orderDict: { 29 | readonly [K in keyof T]: PartialOrd; 30 | }, 31 | ) => PartialOrd> = fromPartialCmp(partialCmp); 32 | export const cmp = ( 33 | orderDict: { 34 | readonly [K in keyof T]: Ord; 35 | }, 36 | ) => 37 | (lhs: TupleN, rhs: TupleN): Ordering => { 38 | const len = Math.min(lhs.length, rhs.length); 39 | let result: Ordering = equal; 40 | for (let i = 0; i < len; i += 1) { 41 | result = and(orderDict[i].cmp(lhs[i], rhs[i]))(result); 42 | } 43 | return result; 44 | }; 45 | export const ord: ( 46 | orderDict: { 47 | readonly [K in keyof T]: Ord; 48 | }, 49 | ) => Ord> = fromCmp(cmp); 50 | -------------------------------------------------------------------------------- /src/type-class.ts: -------------------------------------------------------------------------------- 1 | export * as AbelianGroup from "./type-class/abelian-group.ts"; 2 | export * as AbelianMonoid from "./type-class/abelian-monoid.ts"; 3 | export * as Applicative from "./type-class/applicative.ts"; 4 | export * as Apply from "./type-class/apply.ts"; 5 | export * as Arrow from "./type-class/arrow.ts"; 6 | export * as Associative from "./type-class/associative.ts"; 7 | export * as Bifoldable from "./type-class/bifoldable.ts"; 8 | export * as Bifunctor from "./type-class/bifunctor.ts"; 9 | export * as Bitraversable from "./type-class/bitraversable.ts"; 10 | export * as Category from "./type-class/category.ts"; 11 | export * as Choice from "./type-class/choice.ts"; 12 | export * as Comonad from "./type-class/comonad.ts"; 13 | export * as Conjoined from "./type-class/conjoined.ts"; 14 | export * as Distributive from "./type-class/distributive.ts"; 15 | export * as Endo from "./type-class/endo.ts"; 16 | export * as ErrorMonad from "./type-class/error-monad.ts"; 17 | export * as Eq from "./type-class/eq.ts"; 18 | export * as Field from "./type-class/field.ts"; 19 | export * as FlatMap from "./type-class/flat-map.ts"; 20 | export * as Foldable from "./type-class/foldable.ts"; 21 | export * as Functor from "./type-class/functor.ts"; 22 | export * as Group from "./type-class/group.ts"; 23 | export * as Hash from "./type-class/hash.ts"; 24 | export * as HasInf from "./type-class/has-inf.ts"; 25 | export * as HasNegInf from "./type-class/has-neg-inf.ts"; 26 | export * as Indexable from "./type-class/indexable.ts"; 27 | export * as Indexed from "./type-class/indexed.ts"; 28 | export * as Iso from "./type-class/iso.ts"; 29 | export * as Magma from "./type-class/magma.ts"; 30 | export * as Monad from "./type-class/monad.ts"; 31 | export * as MonadRec from "./type-class/monad-rec.ts"; 32 | export * as Monoid from "./type-class/monoid.ts"; 33 | export * as Monoidal from "./type-class/monoidal.ts"; 34 | export * as Nt from "./type-class/nt.ts"; 35 | export * as Ord from "./type-class/ord.ts"; 36 | export * as PartialEq from "./type-class/partial-eq.ts"; 37 | export * as PartialOrd from "./type-class/partial-ord.ts"; 38 | export * as Profunctor from "./type-class/profunctor.ts"; 39 | export * as Pure from "./type-class/pure.ts"; 40 | export * as Reduce from "./type-class/reduce.ts"; 41 | export * as Representable from "./type-class/representable.ts"; 42 | export * as Reviewable from "./type-class/reviewable.ts"; 43 | export * as Ring from "./type-class/ring.ts"; 44 | export * as SemiGroup from "./type-class/semi-group.ts"; 45 | export * as SemiGroupal from "./type-class/semi-groupal.ts"; 46 | export * as SemiGroupoid from "./type-class/semi-groupoid.ts"; 47 | export * as SemiRing from "./type-class/semi-ring.ts"; 48 | export * as Settable from "./type-class/settable.ts"; 49 | export * as Strong from "./type-class/strong.ts"; 50 | export * as Symmetric from "./type-class/symmetric.ts"; 51 | export * as Tensor from "./type-class/tensor.ts"; 52 | export * as Traversable from "./type-class/traversable.ts"; 53 | export * as Unital from "./type-class/unital.ts"; 54 | export * as Variance from "./type-class/variance.ts"; 55 | -------------------------------------------------------------------------------- /src/type-class/abelian-group.ts: -------------------------------------------------------------------------------- 1 | import type { Group, GroupExceptZero } from "./group.ts"; 2 | import { semiGroupSymbol } from "./semi-group.ts"; 3 | 4 | export const abelSymbol = Symbol("ImplAbel"); 5 | 6 | /** 7 | * A commutative group except zero. 8 | */ 9 | export type AbelianGroupExceptZero = GroupExceptZero & { 10 | readonly [abelSymbol]: true; 11 | }; 12 | 13 | /** 14 | * A commutative group. 15 | */ 16 | export type AbelianGroup = Group & { 17 | readonly [abelSymbol]: true; 18 | }; 19 | 20 | export const trivialAbelianGroup: AbelianGroup = { 21 | combine: () => [], 22 | identity: [], 23 | invert: () => [], 24 | [semiGroupSymbol]: true, 25 | [abelSymbol]: true, 26 | }; 27 | -------------------------------------------------------------------------------- /src/type-class/abelian-monoid.ts: -------------------------------------------------------------------------------- 1 | import type { abelSymbol } from "./abelian-group.ts"; 2 | import type { Monoid } from "./monoid.ts"; 3 | 4 | export type AbelianMonoid = Monoid & { 5 | [abelSymbol]: true; 6 | }; 7 | -------------------------------------------------------------------------------- /src/type-class/applicative.ts: -------------------------------------------------------------------------------- 1 | import { pipe } from "../func.ts"; 2 | import type { Get1 } from "../hkt.ts"; 3 | import { type Apply, makeSemiGroup } from "./apply.ts"; 4 | import type { Monoid } from "./monoid.ts"; 5 | import type { Pure } from "./pure.ts"; 6 | import { semiGroupSymbol } from "./semi-group.ts"; 7 | 8 | /** 9 | * A functor with application. It can combine sequence computations with `apply` or `liftA2` function. 10 | * 11 | * All instances of the applicative `a` must satisfy the following laws: 12 | * 13 | * - Identity: For all `x`; `a.apply(a.pure((i) => i))(x)` equals to `x`, 14 | * - Composition: For all `x`, `y` and `z`; `a.apply(a.apply(a.apply(a.pure((f) => (g) => (i) => f(g(i))))(x))(y))(z)` equals to `a.apply(x)(a.apply(y)(z))`, 15 | * - Homomorphism: For all `f` and `x`; `a.apply(a.pure(f))(a.pure(x))` equals to `a.pure(f(x))`, 16 | * - Interchange: For all `f` and `x`; `a.apply(f)(a.pure(x))` equals to `a.apply(a.pure((i) => i(x)))(f)`. 17 | */ 18 | export type Applicative = Apply & Pure; 19 | 20 | export const makeMonoid = ( 21 | app: Applicative, 22 | ): (m: Monoid) => Monoid> => { 23 | const semi = makeSemiGroup(app); 24 | return (m: Monoid): Monoid> => ({ 25 | combine: semi(m).combine, 26 | identity: app.pure(m.identity), 27 | [semiGroupSymbol]: true, 28 | }); 29 | }; 30 | 31 | export const liftA2 = (app: Applicative) => 32 | ( 33 | f: (a: A) => (b: B) => C, 34 | ): (x: Get1) => (y: Get1) => Get1 => 35 | pipe(app.map(f))(app.apply); 36 | -------------------------------------------------------------------------------- /src/type-class/apply.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ap, 3 | apFirst, 4 | type Apply, 5 | apSecond, 6 | apSelective, 7 | makeSemiGroup, 8 | map2, 9 | } from "./apply.ts"; 10 | import { Option, Ordering } from "../../mod.ts"; 11 | import { assertEquals } from "../../deps.ts"; 12 | 13 | const apply = Option.applicative as Apply; 14 | 15 | Deno.test("ap", () => { 16 | assertEquals( 17 | ap(apply, apply)(Option.some(Option.some(3)))( 18 | Option.some(Option.some((x: number) => x * 2)), 19 | ), 20 | Option.some(Option.some(6)), 21 | ); 22 | }); 23 | Deno.test("apFirst", () => { 24 | assertEquals( 25 | apFirst(apply)(Option.some(3))(Option.some(4)), 26 | Option.some(3), 27 | ); 28 | assertEquals(apFirst(apply)(Option.some(3))(Option.none()), Option.none()); 29 | assertEquals(apFirst(apply)(Option.none())(Option.some(4)), Option.none()); 30 | assertEquals(apFirst(apply)(Option.none())(Option.none()), Option.none()); 31 | }); 32 | Deno.test("apSecond", () => { 33 | assertEquals( 34 | apSecond(apply)(Option.some(3))(Option.some(4)), 35 | Option.some(4), 36 | ); 37 | assertEquals(apSecond(apply)(Option.some(3))(Option.none()), Option.none()); 38 | assertEquals(apSecond(apply)(Option.none())(Option.some(4)), Option.none()); 39 | assertEquals(apSecond(apply)(Option.none())(Option.none()), Option.none()); 40 | }); 41 | Deno.test("apSelective", () => { 42 | assertEquals( 43 | apSelective(apply)("key")(Option.some({ x: 5 }))(Option.some("foo")), 44 | Option.some({ x: 5, key: "foo" }), 45 | ); 46 | assertEquals( 47 | apSelective(apply)("key")(Option.some({ x: 5 }))(Option.none()), 48 | Option.none(), 49 | ); 50 | assertEquals( 51 | apSelective(apply)("key")(Option.none())(Option.some("foo")), 52 | Option.none(), 53 | ); 54 | assertEquals( 55 | apSelective(apply)("key")(Option.none())(Option.none()), 56 | Option.none(), 57 | ); 58 | }); 59 | Deno.test("map2", () => { 60 | const lifted = map2(apply)((x: string) => (y: string) => y + x); 61 | 62 | assertEquals( 63 | lifted(Option.some("foo"))(Option.some("bar")), 64 | Option.some("barfoo"), 65 | ); 66 | assertEquals(lifted(Option.none())(Option.some("bar")), Option.none()); 67 | assertEquals(lifted(Option.some("foo"))(Option.none()), Option.none()); 68 | assertEquals(lifted(Option.none())(Option.none()), Option.none()); 69 | }); 70 | Deno.test("makeSemiGroup", () => { 71 | const m = makeSemiGroup(apply)(Ordering.monoid); 72 | 73 | for (const x of [Ordering.less, Ordering.equal, Ordering.greater]) { 74 | assertEquals( 75 | m.combine(Option.some(Ordering.equal), Option.some(x)), 76 | Option.some(x), 77 | ); 78 | assertEquals( 79 | m.combine(Option.some(Ordering.less), Option.some(x)), 80 | Option.some(Ordering.less), 81 | ); 82 | assertEquals( 83 | m.combine(Option.some(Ordering.greater), Option.some(x)), 84 | Option.some(Ordering.greater), 85 | ); 86 | } 87 | }); 88 | -------------------------------------------------------------------------------- /src/type-class/apply.ts: -------------------------------------------------------------------------------- 1 | import { pipe } from "../func.ts"; 2 | import type { Get1 } from "../hkt.ts"; 3 | import type { Functor } from "./functor.ts"; 4 | import { type SemiGroup, semiGroupSymbol } from "./semi-group.ts"; 5 | 6 | /** 7 | * A structure which able to evaluate a function over `S`. 8 | */ 9 | export type Apply = Functor & { 10 | /** 11 | * Applies the function to the value over `S`. 12 | * 13 | * @param fn - The wrapped function. 14 | * @param t - The wrapped value. 15 | * @returns The value got by evaluating `fn`. 16 | */ 17 | readonly apply: ( 18 | fn: Get1 U>, 19 | ) => (t: Get1) => Get1; 20 | }; 21 | 22 | /** 23 | * Sequences two computations over two functors. 24 | * 25 | * @param applyA - The `Apply` instance for `SA`. 26 | * @param applyB - The `Apply` instance for `SB`. 27 | * @param first - The first computation to be sequenced. 28 | * @param second - The second computation to be sequenced. 29 | * @returns The sequenced computation, doing `first` then `second`. 30 | */ 31 | export const ap = 32 | (applyA: Apply, applyB: Apply) => 33 | (funcT: Get1>) => 34 | (funcM: Get1 U>>): Get1> => 35 | applyA.apply(applyA.map(applyB.apply)(funcM))(funcT); 36 | 37 | /** 38 | * Sequences two computations, discarding the result of `second`. 39 | * 40 | * @param apply - The `Apply` instance for `S`. 41 | * @param first - The first computation to be sequenced. 42 | * @param second - The second computation to be sequenced. 43 | * @returns The sequenced computation, doing `first` then `second`. 44 | */ 45 | export const apFirst = 46 | (apply: Apply) => 47 | (first: Get1): (second: Get1) => Get1 => 48 | apply.apply(apply.map((t: T) => () => t)(first)); 49 | 50 | /** 51 | * Sequences two computations, discarding the result of `first`. 52 | * 53 | * @param apply - The `Apply` instance for `S`. 54 | * @param first - The first computation to be sequenced. 55 | * @param second - The second computation to be sequenced. 56 | * @returns The sequenced computation, doing `first` then `second`. 57 | */ 58 | export const apSecond = 59 | (apply: Apply) => 60 | (first: Get1): (second: Get1) => Get1 => 61 | apply.apply( 62 | apply.map( 63 | () => (u: U) => u, 64 | )(first), 65 | ); 66 | 67 | /** 68 | * Sequences two computations, composing the results of them. 69 | * 70 | * @param apply - The `Apply` instance for `S`. 71 | * @param name - The object key to pick up. 72 | * @param funcT - The computation resulting `T`. 73 | * @param funcU - The computation resulting `U`. 74 | * @returns The composed computation resulting object `T` with an entry of type `U` by `name` key. 75 | */ 76 | export const apSelective = 77 | (apply: Apply) => 78 | (name: Exclude) => 79 | ( 80 | funcT: Get1, 81 | ): ( 82 | funcU: Get1, 83 | ) => Get1 => 84 | apply.apply( 85 | apply.map( 86 | (t: T) => (u: U) => 87 | ({ ...t, [name]: u }) as { 88 | [K in keyof T | N]: K extends keyof T ? T[K] : U; 89 | }, 90 | )(funcT), 91 | ); 92 | 93 | /** 94 | * Lifts up the two-parameter function over `F`. 95 | * 96 | * @param app - The `Apply` instance for `F`. 97 | * @param f - The function which takes two parameters. 98 | * @returns The function lifted over `F`. 99 | */ 100 | export const map2 = (app: Apply) => 101 | ( 102 | f: (a: A) => (b: B) => C, 103 | ): (fa: Get1) => (fb: Get1) => Get1 => 104 | pipe(app.map(f))(app.apply); 105 | 106 | /** 107 | * Lifts up the semi-group instance over the apply functor. 108 | * 109 | * @param apply - The `Apply` instance for `S`. 110 | * @param semi - The `SemiGroup` instance for `T`. 111 | * @returns The lifted `SemiGroup` instance. 112 | */ 113 | export const makeSemiGroup = 114 | (apply: Apply) => (semi: SemiGroup): SemiGroup> => ({ 115 | combine: (l, r) => 116 | apply.apply( 117 | apply.map((left: T) => (right: T) => semi.combine(left, right))( 118 | l, 119 | ), 120 | )(r), 121 | [semiGroupSymbol]: true, 122 | }); 123 | -------------------------------------------------------------------------------- /src/type-class/arrow.ts: -------------------------------------------------------------------------------- 1 | import type { Get2 } from "../hkt.ts"; 2 | import type { Tuple } from "../tuple.ts"; 3 | import { type Category, pipe } from "./category.ts"; 4 | 5 | /** 6 | * A 2-arity kind which can split computation. It useful for receiving any composable computation. 7 | * 8 | * All instances of arrow `a` must satisfy the following laws: 9 | * 10 | * - Identity arrow: `a.arr(Func.id)` equals to `a.identity()`, 11 | * - Composition arrow: For all functions `f` and `g`; `a.arr(Func.compose(f)(g))` equals to `a.compose(a.arr(f))(a.arr(g))`, 12 | * - Left interchange: For all `f`; `a.split(a.arr(f))(a.identity())` equals to `a.arr(a.split(f)(a.identity()))`, 13 | * - Left composition: For all functions `f` and `g`; `a.split(Func.compose(f)(g))(a.identity())` equals to `a.compose(a.split(f)(a.identity()))(a.split(g)(a.identity()))`, 14 | * - Extracting first interchange: For all `f`; `a.compose(a.arr(Tuple.first))(a.split(f)(a.identity()))` equals to `a.compose(f)(a.arr(Tuple.first))`, 15 | * - Independence: For all `f` and `g`; `a.compose(a.arr(Func.split(Func.id)(g)))(a.split(f)(a.identity()))` equals to `a.compose(a.split(f)(a.identity()))(a.arr(Func.split(Func.id)(g)))`, 16 | * - Idempotence: `a.compose(a.arr(Tuple.assocR))(a.split(a.split(f)(a.identity()))(a.identity()))` equals to `a.compose(a.split(f)(a.identity()))(a.arr(Tuple.assocR))`. 17 | */ 18 | export type Arrow = Category & { 19 | /** 20 | * Lifts a function to an arrow. 21 | * 22 | * @param fn - The function to be lifted. 23 | * @returns The new arrow. 24 | */ 25 | readonly arr: (fn: (b: B) => C) => Get2; 26 | /** 27 | * Splits two inputs between two arrows. 28 | * 29 | * @param arrow1 - The arrow to be mapped on the first. 30 | * @param arrow2 - The arrow to be mapped on the second. 31 | * @returns The composed arrow. 32 | */ 33 | readonly split: ( 34 | arrow1: Get2, 35 | ) => ( 36 | arrow2: Get2, 37 | ) => Get2, Tuple>; 38 | }; 39 | 40 | /** 41 | * Embeds the arrow as a first path into a new one. 42 | * 43 | * ```text 44 | * |---------------| 45 | * B -->|-->[ arrow ]-->|--> C 46 | * | | 47 | * D -->|-------------->|--> D 48 | * |---------------| 49 | * ``` 50 | * 51 | * @param a - The `Arrow` instance for `A`. 52 | * @param arrow - The arrow to be mapped. 53 | * @returns The arrow appended a secondary path `D`. 54 | */ 55 | export const first = 56 | (a: Arrow) => 57 | (arrow: Get2): Get2, Tuple> => 58 | a.split(arrow)(a.identity()); 59 | 60 | /** 61 | * Embeds the arrow as a second path into a new one. 62 | * 63 | * ```text 64 | * |---------------| 65 | * D -->|-------------->|--> D 66 | * | | 67 | * B -->|-->[ arrow ]-->|--> C 68 | * |---------------| 69 | * ``` 70 | * 71 | * @param a - The `Arrow` instance for `A`. 72 | * @param arrow - The arrow to be mapped. 73 | * @returns The arrow appended a primary path `D`. 74 | */ 75 | export const second = 76 | (a: Arrow) => 77 | (arrow: Get2): Get2, Tuple> => 78 | a.split(a.identity())(arrow); 79 | 80 | /** 81 | * Sends the input to both arrows and packs their output. 82 | * 83 | * ```text 84 | * |-------------------| 85 | * | |-->[ arrow1 ]-->|--> C1 86 | * B -->|--| | 87 | * | |-->[ arrow2 ]-->|--> C2 88 | * |-------------------| 89 | * ``` 90 | * 91 | * @param a - The `Arrow` instance for `A`. 92 | * @param arrow1 - The arrow used on the first item. 93 | * @param arrow2 - The arrow used on the second item. 94 | * @returns The joined arrow. 95 | */ 96 | export const fanOut = 97 | (a: Arrow) => 98 | (arrow1: Get2) => 99 | (arrow2: Get2): Get2> => 100 | pipe(a)(a.arr((b: B): Tuple => [b, b]))(a.split(arrow1)(arrow2)); 101 | -------------------------------------------------------------------------------- /src/type-class/associative.ts: -------------------------------------------------------------------------------- 1 | import type { Get2 } from "../hkt.ts"; 2 | import type { GenericBifunctor } from "./bifunctor.ts"; 3 | import type { Category } from "./category.ts"; 4 | import type { Iso } from "./iso.ts"; 5 | 6 | export type Associative = 7 | & Category 8 | & GenericBifunctor 9 | & { 10 | readonly assoc: () => Iso< 11 | Cat, 12 | Get2>, 13 | Get2, C> 14 | >; 15 | }; 16 | -------------------------------------------------------------------------------- /src/type-class/bifunctor.ts: -------------------------------------------------------------------------------- 1 | import { id } from "../func.ts"; 2 | import type { Get2 } from "../hkt.ts"; 3 | import type { Category } from "./category.ts"; 4 | 5 | /** 6 | * A structure which lifts both type parameters on `P`. 7 | * 8 | * All instances of bifunctor `f` mist satisfy the following laws: 9 | * 10 | * - Identity: `f.biMap(id)(id)` equals to `id`, 11 | * - Composition: For all `f`, `g`, `h` and `i`; `f.biMap(compose(f)(g))(compose(h)(i))` equals to `compose(f.biMap(f)(h))(f.biMap(g)(i))`. 12 | */ 13 | export type Bifunctor

= { 14 | readonly biMap: ( 15 | first: (a: A) => B, 16 | ) => (second: (c: C) => D) => (curr: Get2) => Get2; 17 | }; 18 | 19 | export const first = 20 |

(bi: Bifunctor

) => 21 | (fn: (a: A) => B): (curr: Get2) => Get2 => 22 | bi.biMap(fn)(id); 23 | 24 | export const second =

( 25 | bi: Bifunctor

, 26 | ): (fn: (a: B) => C) => (curr: Get2) => Get2 => 27 | bi.biMap(id); 28 | 29 | export type GenericBifunctor = { 30 | readonly cat1: Category; 31 | readonly cat2: Category; 32 | readonly cat3: Category; 33 | 34 | readonly genericBiMap: ( 35 | first: Get2, 36 | ) => ( 37 | second: Get2, 38 | ) => Get2, Get2>; 39 | }; 40 | 41 | export const genericLeftMap = 42 | (gb: GenericBifunctor) => 43 | (f: Get2): Get2, Get2> => 44 | gb.genericBiMap(f)(gb.cat2.identity()); 45 | export const genericRightMap = ( 46 | gb: GenericBifunctor, 47 | ): (f: Get2) => Get2, Get2> => 48 | gb.genericBiMap(gb.cat1.identity()); 49 | -------------------------------------------------------------------------------- /src/type-class/bitraversable.ts: -------------------------------------------------------------------------------- 1 | import type { Get1, Get2 } from "../hkt.ts"; 2 | import { id } from "../identity.ts"; 3 | import type { Applicative } from "./applicative.ts"; 4 | import type { Bifoldable } from "./bifoldable.ts"; 5 | import type { Bifunctor } from "./bifunctor.ts"; 6 | 7 | export type Bitraversable = Bifunctor & Bifoldable & { 8 | bitraverse: ( 9 | app: Applicative, 10 | ) => ( 11 | f: (a: A) => Get1, 12 | ) => ( 13 | g: (b: B) => Get1, 14 | ) => (data: Get2) => Get1>; 15 | }; 16 | 17 | export const bisequenceA = 18 | (bi: Bitraversable, app: Applicative) => 19 | (data: Get2, Get1>): Get1> => 20 | bi.bitraverse(app)(id>)(id>)(data); 21 | -------------------------------------------------------------------------------- /src/type-class/category.ts: -------------------------------------------------------------------------------- 1 | import type { Get2 } from "../hkt.ts"; 2 | import type { SemiGroupoid } from "./semi-groupoid.ts"; 3 | 4 | /** 5 | * A 2-arity kind which consists of objects and arrows between them. 6 | * 7 | * All instances of category `c` must satisfy the following laws: 8 | * 9 | * - Right identity: For all `f`; `c.compose(f)(c.identity())` equals to `f`, 10 | * - Left identity: For all `f`; `c.compose(c.identity())(f)` equals to `f`, 11 | * - Associativity: For all `f`, `g` and `h`; `c.compose(f)(c.compose(g)(h))` equals to `c.compose(c.compose(f)(g))(h)`. 12 | */ 13 | export type Category = SemiGroupoid & { 14 | readonly identity: () => Get2; 15 | }; 16 | 17 | export const compose = ( 18 | cat: Category, 19 | ): (bc: Get2) => (ab: Get2) => Get2 => 20 | cat.compose; 21 | 22 | export const pipe = 23 | (cat: Category) => 24 | (bc: Get2) => 25 | (ab: Get2): Get2 => cat.compose(ab)(bc); 26 | -------------------------------------------------------------------------------- /src/type-class/choice.ts: -------------------------------------------------------------------------------- 1 | import type { Get2 } from "../hkt.ts"; 2 | import type { Result } from "../result.ts"; 3 | import type { Profunctor } from "./profunctor.ts"; 4 | 5 | export type Choice

= Profunctor

& { 6 | readonly left: ( 7 | curr: Get2, 8 | ) => Get2, Result>; 9 | readonly right: ( 10 | curr: Get2, 11 | ) => Get2, Result>; 12 | }; 13 | -------------------------------------------------------------------------------- /src/type-class/comonad.ts: -------------------------------------------------------------------------------- 1 | import { compose } from "../func.ts"; 2 | import type { Get1 } from "../hkt.ts"; 3 | import type { Functor } from "./functor.ts"; 4 | 5 | /** 6 | * A dual of Monad, the framework of computing neighbor states in parallel. 7 | * 8 | * All instances of the comonad `c` must satisfy the following laws: 9 | * 10 | * - Duplicate then extract: For all `x`; `c.extract(c.duplicate(x))` equals to `x`, 11 | * - Extract as identity of map: For all `x`; `c.map(c.extract)(c.duplicate(x))` equals to `x`, 12 | * - Duplicate as identity of map: For all `x`; `c.duplicate(c.duplicate(x))` equals to `c.map(c.duplicate)(c.duplicate(x))`. 13 | */ 14 | export type Comonad = Functor & { 15 | /** 16 | * Extracts the internal state of type `A`. 17 | * 18 | * @param wa - The wrapped object about type `A`. 19 | * @returns The value of type `A`. 20 | */ 21 | readonly extract: (wa: Get1) => A; 22 | /** 23 | * Computes the surrounding states from a source object `wa`. 24 | * 25 | * @param wa - The wrapped object about type `A`. 26 | * @returns The surrounding states of `wa`. 27 | */ 28 | readonly duplicate: (wa: Get1) => Get1>; 29 | }; 30 | 31 | export const extend = (comonad: Comonad) => 32 | ( 33 | extension: (wa: Get1) => A2, 34 | ): (wa: Get1) => Get1 => 35 | compose(comonad.map(extension))(comonad.duplicate); 36 | -------------------------------------------------------------------------------- /src/type-class/conjoined.ts: -------------------------------------------------------------------------------- 1 | import type { Get1, Get2 } from "../hkt.ts"; 2 | import type { Choice } from "./choice.ts"; 3 | import type { Comonad } from "./comonad.ts"; 4 | import type { Corep, Corepresentable } from "./corepresentable.ts"; 5 | import type { Functor } from "./functor.ts"; 6 | 7 | export type Conjoined

= 8 | & Choice

9 | & Corepresentable

10 | & Comonad> 11 | & { 12 | readonly distribute: ( 13 | f: Functor, 14 | ) => (pab: Get2) => Get2, Get1>; 15 | }; 16 | -------------------------------------------------------------------------------- /src/type-class/corepresentable.ts: -------------------------------------------------------------------------------- 1 | import type { Get1, Get2, Hkt0, Hkt1 } from "../hkt.ts"; 2 | import type { Functor } from "./functor.ts"; 3 | import type { Profunctor } from "./profunctor.ts"; 4 | 5 | /** 6 | * An HKT extension to provide associated type `Corep` for `Corepresentable`. 7 | */ 8 | export interface HktCorep { 9 | readonly corep: Hkt1; // Representation type 10 | } 11 | 12 | export type ApplyCorep = P extends Hkt0 ? P & { readonly corep: H } 13 | : never; 14 | export type Corep

= P extends HktCorep ? P["corep"] : never; 15 | export type GetCorep = Get1, T>; 16 | 17 | export type Corepresentable

= Profunctor

& { 18 | readonly functor: Functor>; 19 | readonly coindex: ( 20 | pab: Get2, 21 | ) => (corep: GetCorep) => B; 22 | readonly cotabulate: ( 23 | f: (corep: GetCorep) => B, 24 | ) => Get2; 25 | }; 26 | -------------------------------------------------------------------------------- /src/type-class/distributive.ts: -------------------------------------------------------------------------------- 1 | import type { Get1 } from "../hkt.ts"; 2 | import type { Functor } from "./functor.ts"; 3 | import type { Monad } from "./monad.ts"; 4 | 5 | /** 6 | * The dual of traversable functor, allows zipping a nested structure efficiently. 7 | * 8 | * All instances of the distributive functor `g` must satisfy the following laws: 9 | * 10 | * - Identity: For all functor `f` and data `x`; `distribute(f)(x)` equals to `distribute(f)(map(id)(x))`, 11 | * - Reversibility: For all distributive functor `f` and data `x`; `g.distribute(f)(f.distribute(g)(x))` equals to `x`. 12 | */ 13 | export type Distributive = Functor & { 14 | /** 15 | * The dual of `sequenceA` of traversable functor. 16 | * 17 | * @param functor - The `Functor` instance for `F`. 18 | * @param fga - Data of type `A` on `F>`. 19 | * @returns The distributed type onto `G>`. 20 | */ 21 | readonly distribute: ( 22 | functor: Functor, 23 | ) => (fga: Get1>) => Get1>; 24 | }; 25 | 26 | export const collect = 27 | (dist: Distributive) => 28 | (functor: Functor) => 29 | (f: (a: A) => Get1) => 30 | (fa: Get1): Get1> => 31 | dist.distribute(functor)(functor.map(f)(fa)); 32 | 33 | export const distributeM = 34 | (dist: Distributive) => 35 | (monad: Monad) => 36 | (mga: Get1>): Get1> => 37 | dist.distribute(monad)(mga); 38 | 39 | export const collectM = 40 | (dist: Distributive) => 41 | (monad: Monad) => 42 | (f: (a: A) => Get1) => 43 | (fa: Get1): Get1> => 44 | dist.distribute(monad)(monad.map(f)(fa)); 45 | 46 | export const contraverse = 47 | (dist: Distributive) => 48 | (functor: Functor) => 49 | (f: (fa: Get1) => B) => 50 | (fga: Get1>): Get1 => 51 | dist.map(f)(dist.distribute(functor)(fga)); 52 | 53 | export const coMapM = 54 | (dist: Distributive) => 55 | (monad: Monad) => 56 | (f: (ma: Get1) => B) => 57 | (mga: Get1>): Get1 => 58 | dist.map(f)(distributeM(dist)(monad)(mga)); 59 | -------------------------------------------------------------------------------- /src/type-class/endo.ts: -------------------------------------------------------------------------------- 1 | import { compose } from "../func.ts"; 2 | import type { Monoid } from "./monoid.ts"; 3 | import { semiGroupSymbol } from "./semi-group.ts"; 4 | 5 | export type Endo = (t: T) => T; 6 | 7 | export const monoid = (): Monoid> => ({ 8 | identity: (x: T) => x, 9 | combine: (l, r) => compose(l)(r), 10 | [semiGroupSymbol]: true, 11 | }); 12 | -------------------------------------------------------------------------------- /src/type-class/eq.ts: -------------------------------------------------------------------------------- 1 | import type { Get1 } from "../hkt.ts"; 2 | import type { PartialEq } from "./partial-eq.ts"; 3 | 4 | export const eqSymbol = Symbol("ImplEq"); 5 | 6 | /** 7 | * All instances of `Eq` must satisfy the following conditions: 8 | * - Symmetric: `Eq` always equals to `Eq`. 9 | * - Transitive: `Eq` and `Eq` always implies `Eq`. 10 | * - Reflexive: Passing two same values to `Eq` always returns `true`. 11 | * 12 | * For example, the comparator below cannot implement `Eq` because that does not satisfy reflexive due to `NaN === NaN` always be false. 13 | * 14 | * ```ts 15 | * import { PartialEq } from "./partial-eq.ts"; 16 | * 17 | * const numPartialEq: PartialEq = { 18 | * eq: (x, y) => x === y, 19 | * }; 20 | * ``` 21 | */ 22 | export type Eq = PartialEq & { 23 | readonly [eqSymbol]: true; 24 | }; 25 | 26 | export const stringEq: Eq = { 27 | eq: (l, r) => l === r, 28 | [eqSymbol]: true, 29 | }; 30 | 31 | export const fromEquality = 32 | (equality: (x: X) => (l: Lhs, r: Rhs) => boolean) => 33 | (x: X): Eq => ({ 34 | eq: equality(x), 35 | [eqSymbol]: true, 36 | }); 37 | 38 | export const fromProjection = 39 | (projection: (structure: Get1) => X) => 40 | (equality: Eq): Eq, Get1> => ({ 41 | eq: (l, r) => equality.eq(projection(l), projection(r)), 42 | [eqSymbol]: true, 43 | }); 44 | -------------------------------------------------------------------------------- /src/type-class/error-monad.ts: -------------------------------------------------------------------------------- 1 | import { type CatT, catT } from "../cat.ts"; 2 | import type { Get1 } from "../hkt.ts"; 3 | import type { Result } from "../result.ts"; 4 | import type { Monad } from "./monad.ts"; 5 | 6 | /** 7 | * A monad which allows making the computation value into a `Result` with an error context message. 8 | */ 9 | export type ErrorMonad = Monad & { 10 | /** 11 | * Converts a computation value into a `Result` with a message `context`. 12 | * 13 | * @param context - The message to be included to an error. 14 | * @param computation - The extracting computation with a value. 15 | * @returns The extracted value or an `Error` if failed. 16 | */ 17 | readonly context: ( 18 | context: string, 19 | ) => (computation: Get1) => Result; 20 | /** 21 | * Converts a computation value into a `Result` with a function `fn` creating a message. 22 | * 23 | * @param fn - The function to create a message to be included to an error. 24 | * @param computation - The extracting computation with a value. 25 | * @returns The extracted value or an `Error` if failed. 26 | */ 27 | readonly withContext: ( 28 | fn: () => string, 29 | ) => (computation: Get1) => Result; 30 | }; 31 | 32 | /** 33 | * A `CatT` which helps you to handle a fail-able computation with an error message. Your provided context message will be used in message value of an `Error` object. 34 | */ 35 | export type ErrorCat = 36 | & CatT 37 | & Readonly<{ 38 | context: (context: string) => Result; 39 | withContext: (fn: () => string) => Result; 40 | }>; 41 | 42 | /** 43 | * Creates a `ErrorCat` from the `ErrorMonad` instance and environment. 44 | * 45 | * @param monad - The `ErrorMonad` instance for `M`, 46 | * @param value - The computation environment. 47 | * @returns The `ErrorCat` instance. 48 | */ 49 | export const errorCat = (monad: ErrorMonad) => 50 | ( 51 | env: Get1, 52 | ): ErrorCat => ({ 53 | ...catT(monad)(env), 54 | context: (context) => monad.context(context)(env), 55 | withContext: (fn) => monad.withContext(fn)(env), 56 | }); 57 | 58 | /** 59 | * Creates a `ErrorCat` from the `ErrorMonad` instance. 60 | * 61 | * @param monad - The `ErrorMonad` instance for `M`, 62 | * @returns The `ErrorCat` instance with an empty environment. 63 | */ 64 | export const doError = ( 65 | monad: ErrorMonad, 66 | ): ErrorCat> => 67 | errorCat(monad)(monad.pure({} as Record)); 68 | -------------------------------------------------------------------------------- /src/type-class/field.ts: -------------------------------------------------------------------------------- 1 | import type { AbelianGroupExceptZero } from "./abelian-group.ts"; 2 | import type { Ring } from "./ring.ts"; 3 | 4 | /** 5 | * A structure which can operate addition, subtraction, multiplication and division. 6 | * 7 | * All instance of `Ring` must satisfy following conditions: 8 | * 9 | * - Associative on addition: for all `x`, `y` and `z`; `additive.combine(additive.combine(x, y), z)` equals to `additive.combine(x, additive.combine(y, z))` 10 | * - Identity on addition: for all `x`; `additive.combine(additive.identity, x)` equals to `x`. 11 | * - Inverse on addition: for all `x`; exists `y`; `additive.combine(x, y)` equals to `additive.identity`. 12 | * - Commutative on addition: for all `x` and `y`; `additive.combine(x, y)` equals to `additive.combine(y, x)`. 13 | * - Associative on multiplication: for all `x`, `y` and `z` except zero; `multiplication.combine(multiplication.combine(x, y), z)` equals to `multiplication.combine(x, multiplication.combine(y, z))` 14 | * - Identity on multiplication: for all `x` except zero; `multiplication.combine(multiplication.identity, x)` equals to `multiplication.combine(x, multiplication.identity)` and `x`. 15 | * - Inverse on multiplication: for all `x` except zero; exists `y`; `multiplication.combine(x, y)` equals to `multiplication.combine(y, x)` and `multiplication.identity`. 16 | * - Distributive: for all `x`, `y` and `z`; `multiplication.combine(x, additive.combine(y, z))` equals to `additive.combine(multiplication.combine(x, y), multiplication.combine(x, z))` 17 | */ 18 | export type Field = Ring & { 19 | multiplication: AbelianGroupExceptZero; 20 | }; 21 | -------------------------------------------------------------------------------- /src/type-class/flat-map.ts: -------------------------------------------------------------------------------- 1 | import type { Get1 } from "../hkt.ts"; 2 | 3 | export type FlatMap = { 4 | readonly flatMap: ( 5 | a: (t: T1) => Get1, 6 | ) => (t: Get1) => Get1; 7 | }; 8 | 9 | export const flatten = ( 10 | f: FlatMap, 11 | ): (t: Get1>) => Get1 => f.flatMap((t) => t); 12 | -------------------------------------------------------------------------------- /src/type-class/functor.ts: -------------------------------------------------------------------------------- 1 | import { constant } from "../func.ts"; 2 | import type { Get1 } from "../hkt.ts"; 3 | import type { Invariant } from "./variance.ts"; 4 | 5 | /** 6 | * A structure which able to lift up in `F`. 7 | * 8 | * All instances of the functor `f` must satisfy the following laws: 9 | * 10 | * - Identity: `f.map((x) => x)` equals to `(x) => x`, 11 | * - Composition: For all `a` and `b`; `f.map((x) => b(a(x)))` equals to `(x) => f.map(b)(f.map(a)(x))`. 12 | */ 13 | export type Functor = { 14 | /** 15 | * Maps the function `fn` onto `F` structure. 16 | * 17 | * @param fn - The function to be mapped. 18 | * @returns The mapped function. 19 | */ 20 | readonly map: (fn: (t: T) => U) => (t: Get1) => Get1; 21 | }; 22 | 23 | /** 24 | * Maps a function into the nested one with two functor instances. 25 | * 26 | * @param funcA - The instance of `Functor` for `FA`. 27 | * @param funcB - The instance of `Functor` for `FB`. 28 | * @param f - The mapper function. 29 | * @returns The nest-mapped function. 30 | */ 31 | export const map = (funcA: Functor, funcB: Functor) => 32 | ( 33 | f: (t: T) => U, 34 | ): (funcT: Get1>) => Get1> => 35 | funcA.map(funcB.map(f)); 36 | 37 | /** 38 | * @param func - The instance of `Functor` for `F`. 39 | * @param a - The value to be replaced. 40 | * @param fb - The target to replace. 41 | * @returns The replaced object of `F` contains the item `a`. 42 | */ 43 | export const replace = 44 | (func: Functor) => (a: A): (fb: Get1) => Get1 => 45 | func.map(constant(a)); 46 | 47 | /** 48 | * Applies the function in `S` to the item `t`, but flipped the arguments. 49 | * 50 | * @param func - The instance of `Functor` for `S`. 51 | * @param item - The item to be applied. 52 | * @param t - The function to apply. 53 | * @returns The applied item in `S`. 54 | */ 55 | export const flap = 56 | (func: Functor) => 57 | (item: T): (t: Get1 U>) => Get1 => 58 | func.map((f: (argT: T) => U) => f(item)); 59 | 60 | /** 61 | * Binds the item in `S` to a new object with the provided key. 62 | * 63 | * @param func - The instance of `Functor` for `S`. 64 | * @param name - The property key to be bound. 65 | * @param t - The item in `S`. 66 | * @returns The bound object in `S`. 67 | */ 68 | export const bindTo = (func: Functor) => 69 | ( 70 | name: N, 71 | ): (t: Get1) => Get1> => 72 | func.map((a: T) => ({ [name]: a }) as Record); 73 | 74 | /** 75 | * @param func - The instance of `Functor` for `S`. 76 | * @returns The instance of `Invariant` for `S` of `Functor`. 77 | */ 78 | export const functorAsInvariant = (func: Functor): Invariant => ({ 79 | inMap: (f) => () => func.map(f), 80 | }); 81 | -------------------------------------------------------------------------------- /src/type-class/group.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals, assertThrows } from "../../deps.ts"; 2 | import { none, type Option, some } from "../option.ts"; 3 | import { type GroupExceptZero, powiEZ, subtractEZ } from "./group.ts"; 4 | import { semiGroupSymbol } from "./semi-group.ts"; 5 | 6 | type Matrix = [a: number, b: number, c: number, d: number]; 7 | 8 | const matrixGroup: GroupExceptZero = { 9 | identity: [1, 0, 0, 1], 10 | combine([l11, l12, l21, l22], [r11, r12, r21, r22]): Matrix { 11 | return [ 12 | l11 * r11 + l12 * r21, 13 | l11 * r12 + l12 * r22, 14 | l21 * r11 + l22 * r21, 15 | l21 * r12 + l22 * r22, 16 | ]; 17 | }, 18 | invert([m11, m12, m21, m22]): Option { 19 | const det = m11 * m22 - m12 * m21; 20 | if (det === 0) { 21 | return none(); 22 | } 23 | return some([m22 / det, -m12 / det, -m21 / det, m11 / det]); 24 | }, 25 | [semiGroupSymbol]: true, 26 | }; 27 | 28 | Deno.test("subtract", () => { 29 | assertEquals( 30 | subtractEZ(matrixGroup)([1, 2, 3, 4])([5, 6, 7, 8]), 31 | some([3, -2, 2, -1] as Matrix), 32 | ); 33 | }); 34 | 35 | Deno.test("powi", () => { 36 | assertEquals( 37 | powiEZ(matrixGroup)([1, 2, 3, 4])(11), 38 | some([25699957, 37455814, 56183721, 81883678] as Matrix), 39 | ); 40 | assertEquals( 41 | powiEZ(matrixGroup)([1, 2, 3, 4])(4), 42 | some([199, 290, 435, 634] as Matrix), 43 | ); 44 | assertEquals( 45 | powiEZ(matrixGroup)([1, 2, 3, 4])(0), 46 | some([1, 0, 0, 1] as Matrix), 47 | ); 48 | assertEquals( 49 | powiEZ(matrixGroup)([1, 2, 3, 4])(-9), 50 | some( 51 | [ 52 | -1418567 / 256, 53 | 648891 / 256, 54 | 1946673 / 512, 55 | -890461 / 512, 56 | ] as Matrix, 57 | ), 58 | ); 59 | assertThrows(() => { 60 | powiEZ(matrixGroup)([1, 2, 3, 4])(0.5); 61 | }, "`exp` must be an integer"); 62 | }); 63 | -------------------------------------------------------------------------------- /src/type-class/group.ts: -------------------------------------------------------------------------------- 1 | import { map, type Option, some, unwrap } from "../option.ts"; 2 | import type { Monoid } from "./monoid.ts"; 3 | import { semiGroupSymbol } from "./semi-group.ts"; 4 | 5 | /** 6 | * A structure of 2-term operation `combine` and 1-term operation `invert`, which must satisfy following conditions **except zero element**: 7 | * 8 | * - Associative: for all `x`, `y` and `z`; `combine(combine(x, y), z)` equals to `combine(x, combine(y, z))`. 9 | * - Identity: for all `x`; `combine(x, identity)` equals to `combine(identity, x)` and `x`. 10 | * - Inverse: for all `x`; `combine(x, invert(x))` equals to `combine(invert(x), x)` and `identity`. 11 | */ 12 | export type GroupExceptZero = Monoid & { 13 | readonly invert: (g: G) => Option; 14 | }; 15 | 16 | export const subtractEZ = 17 | (group: GroupExceptZero) => (l: G) => (r: G): Option => 18 | map((inv: G) => group.combine(l, inv))(group.invert(r)); 19 | 20 | export const powiEZ = 21 | (group: GroupExceptZero) => (base: G) => (exp: number): Option => { 22 | if (!Number.isInteger(exp)) { 23 | throw new Error("`exp` must be an integer"); 24 | } 25 | const g = (x: G, n: number, c: G): G => { 26 | if (n % 2 === 0) { 27 | return g(group.combine(x, x), Math.floor(n / 2), c); 28 | } 29 | if (n === 1) { 30 | return group.combine(x, c); 31 | } 32 | return g( 33 | group.combine(x, x), 34 | Math.floor(n / 2), 35 | group.combine(x, c), 36 | ); 37 | }; 38 | const f = (x: G, n: number): G => { 39 | if (n % 2 === 0) { 40 | return f(group.combine(x, x), Math.floor(n / 2)); 41 | } 42 | if (n === 1) { 43 | return x; 44 | } 45 | return g(group.combine(x, x), Math.floor(n / 2), x); 46 | }; 47 | if (exp === 0) { 48 | return some(group.identity); 49 | } 50 | if (exp < 0) { 51 | return group.invert(f(base, -exp)); 52 | } 53 | return some(f(base, exp)); 54 | }; 55 | 56 | /** 57 | * A structure of 2-term operation `combine` and 1-term operation `invert`, which must satisfy following conditions: 58 | * 59 | * - Associative: for all `x`, `y` and `z`; `combine(combine(x, y), z)` equals to `combine(x, combine(y, z))`. 60 | * - Identity: for all `x`; `combine(x, identity)` equals to `combine(identity, x)` and `x`. 61 | * - Inverse: for all `x`; `combine(x, invert(x))` equals to `combine(invert(x), x)` and `identity`. 62 | */ 63 | export type Group = Monoid & { 64 | readonly invert: (g: G) => G; 65 | }; 66 | 67 | export const toGroupExceptZero = (group: Group): GroupExceptZero => ({ 68 | ...group, 69 | invert: (g) => some(group.invert(g)), 70 | }); 71 | 72 | export const subtract = (group: Group) => (l: G) => (r: G): G => 73 | group.combine(l, group.invert(r)); 74 | 75 | export const powi = (group: Group) => (base: G) => (exp: number): G => 76 | unwrap(powiEZ(toGroupExceptZero(group))(base)(exp)); 77 | 78 | export const trivialGroup: Group = { 79 | combine: () => [], 80 | identity: [], 81 | invert: () => [], 82 | [semiGroupSymbol]: true, 83 | }; 84 | 85 | export const addGroup: Group = { 86 | combine: (l: number, r: number) => l + r, 87 | identity: 0, 88 | invert: (x: number) => -x, 89 | [semiGroupSymbol]: true, 90 | }; 91 | -------------------------------------------------------------------------------- /src/type-class/has-inf.ts: -------------------------------------------------------------------------------- 1 | import { isLt } from "../ordering.ts"; 2 | import type { Monoid } from "./monoid.ts"; 3 | import type { Ord } from "./ord.ts"; 4 | import { semiGroupSymbol } from "./semi-group.ts"; 5 | 6 | export type HasInf = Ord & { 7 | readonly infinity: A; 8 | }; 9 | 10 | export const minMonoid = (order: HasInf): Monoid => ({ 11 | combine(l, r): A { 12 | return isLt(order.cmp(l, r)) ? l : r; 13 | }, 14 | identity: order.infinity, 15 | [semiGroupSymbol]: true, 16 | }); 17 | -------------------------------------------------------------------------------- /src/type-class/has-neg-inf.ts: -------------------------------------------------------------------------------- 1 | import { isLt } from "../ordering.ts"; 2 | import type { Monoid } from "./monoid.ts"; 3 | import type { Ord } from "./ord.ts"; 4 | import { semiGroupSymbol } from "./semi-group.ts"; 5 | 6 | export type HasNegInf = Ord & { 7 | readonly negativeInfinity: A; 8 | }; 9 | 10 | export const maxMonoid = (order: HasNegInf): Monoid => ({ 11 | combine(l, r): A { 12 | return isLt(order.cmp(l, r)) ? r : l; 13 | }, 14 | identity: order.negativeInfinity, 15 | [semiGroupSymbol]: true, 16 | }); 17 | -------------------------------------------------------------------------------- /src/type-class/indexable.ts: -------------------------------------------------------------------------------- 1 | import { constant, type Fn, type FnHkt } from "../func.ts"; 2 | import type { Get2 } from "../hkt.ts"; 3 | import type { Profunctor } from "./profunctor.ts"; 4 | 5 | export type Indexable = Profunctor

& { 6 | readonly indexed: (data: Get2) => (index: I) => (a: A) => B; 7 | }; 8 | 9 | export const fnIndexable = (): Indexable => ({ 10 | indexed: constant, 11 | diMap: 12 | (f: (a: A) => B) => 13 | (g: (c: C) => D) => 14 | (m: Fn): Fn => 15 | (a) => g(m(f(a))), 16 | }); 17 | -------------------------------------------------------------------------------- /src/type-class/indexed.ts: -------------------------------------------------------------------------------- 1 | import type { Apply3Only, Hkt3 } from "../hkt.ts"; 2 | import type { Indexable } from "./indexable.ts"; 3 | 4 | export type Indexed = (i: I) => (a: A) => B; 5 | 6 | export interface IndexedHkt extends Hkt3 { 7 | readonly type: Indexed; 8 | } 9 | export const diMap: ( 10 | f: (a: A) => B, 11 | ) => (g: (c: C) => D) => (m: Indexed) => Indexed = 12 | (f) => (g) => (m) => (i) => (a) => g(m(i)(f(a))); 13 | 14 | export const indexedIndexable = (): Indexable< 15 | I, 16 | Apply3Only 17 | > => ({ 18 | indexed: (data) => data, 19 | diMap, 20 | }); 21 | -------------------------------------------------------------------------------- /src/type-class/iso.ts: -------------------------------------------------------------------------------- 1 | import type { Get2 } from "../hkt.ts"; 2 | 3 | /** 4 | * All instance of `Iso` must satisfy: 5 | * 6 | * - For all `x` of `A`; `backward(forward(x)) == x`, 7 | * - For all `x` of `B`; `forward(backward(x)) == x`. 8 | */ 9 | export type Iso = { 10 | readonly forward: Get2; 11 | readonly backward: Get2; 12 | }; 13 | -------------------------------------------------------------------------------- /src/type-class/magma.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 2-term operation with no constraints, but must be closed under `T`. 3 | */ 4 | export type Magma = { 5 | readonly combine: (l: T, r: T) => T; 6 | }; 7 | -------------------------------------------------------------------------------- /src/type-class/monad-rec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type ControlFlow, 3 | isContinue, 4 | newBreak, 5 | newContinue, 6 | } from "../control-flow.ts"; 7 | import type { Get1 } from "../hkt.ts"; 8 | import { mapOrElse, type Option } from "../option.ts"; 9 | import { replace } from "./functor.ts"; 10 | import type { Monad } from "./monad.ts"; 11 | import type { Monoid } from "./monoid.ts"; 12 | 13 | /** 14 | * An extended `Monad` also supports the `tailRecM` operation. 15 | */ 16 | export type MonadRec = Monad & { 17 | /** 18 | * Executes a `stepper` while it returns a `Continue`. This exits only if `stepper` returned a `Break` and forwards it. 19 | * 20 | * Almost all of the `MonadRec` instances are implemented with a simple `while` loop because JavaScript runtime rarely optimizes a tail call. 21 | * 22 | * @param stepper - A function to run with its control flow. 23 | * @returns The execution result. 24 | */ 25 | readonly tailRecM: ( 26 | stepper: (state: A) => Get1>, 27 | ) => (state: A) => Get1; 28 | }; 29 | 30 | export const tailRecM2 = (m: MonadRec) => 31 | ( 32 | stepper: (a: A) => (b: B) => Get1>, 33 | ) => 34 | (a: A) => 35 | (b: B): Get1 => 36 | m.tailRecM(([a, b]: readonly [A, B]) => stepper(a)(b))([a, b]); 37 | 38 | export const tailRecM3 = (m: MonadRec) => 39 | ( 40 | stepper: ( 41 | a: A, 42 | ) => (b: B) => (c: C) => Get1>, 43 | ) => 44 | (a: A) => 45 | (b: B) => 46 | (c: C): Get1 => 47 | m.tailRecM(([a, b, c]: readonly [A, B, C]) => stepper(a)(b)(c))([ 48 | a, 49 | b, 50 | c, 51 | ]); 52 | 53 | /** 54 | * A `MonadRec` instance for `Identity`. 55 | */ 56 | export const tailRec = (stepper: (a: A) => ControlFlow) => 57 | ( 58 | initialA: A, 59 | ): X => { 60 | let flow = stepper(initialA); 61 | while (isContinue(flow)) { 62 | flow = stepper(flow[1]); 63 | } 64 | return flow[1]; 65 | }; 66 | 67 | export const tailRec2 = ( 68 | stepper: (a: A) => (b: B) => ControlFlow, 69 | ) => 70 | (initialA: A) => 71 | (initialB: B): X => 72 | tailRec(([a, b]: readonly [A, B]) => stepper(a)(b))([ 73 | initialA, 74 | initialB, 75 | ]); 76 | 77 | export const tailRec3 = ( 78 | stepper: (a: A) => (b: B) => (c: C) => ControlFlow, 79 | ) => 80 | (initialA: A) => 81 | (initialB: B) => 82 | (initialC: C): X => 83 | tailRec(([a, b, c]: readonly [A, B, C]) => stepper(a)(b)(c))([ 84 | initialA, 85 | initialB, 86 | initialC, 87 | ]); 88 | 89 | /** 90 | * Starts an infinite loop of the operation `op`. 91 | * 92 | * @param m - A `MonadRec` instance for `M`. 93 | * @returns The infinite loop operation. 94 | */ 95 | export const forever = 96 | (m: MonadRec) => (op: Get1): Get1 => 97 | m.tailRecM((state: never[]): Get1> => 98 | replace(m)(newContinue(state))(op) 99 | )([]); 100 | 101 | /** 102 | * Executes an optional operation `optionOp` while it returns a `Some` value. The accumulated result of it will be returned. 103 | * 104 | * @param mon - A `Monoid` instance for `T`. 105 | * @param m - A `MonadRec` instance for `M`. 106 | * @param optionOp - An optional operation. 107 | * @returns The looping and accumulating operation. 108 | */ 109 | export const whileSome = 110 | (mon: Monoid) => 111 | (m: MonadRec) => 112 | (optionOp: Get1>): Get1 => 113 | m.tailRecM((state: T): Get1> => 114 | m.map( 115 | mapOrElse((): ControlFlow => newBreak(state))( 116 | (item: T) => newContinue(mon.combine(state, item)), 117 | ), 118 | )(optionOp) 119 | )(mon.identity); 120 | 121 | /** 122 | * Executes an optional operation `optionOp` until it returns a `Some` value. 123 | * 124 | * @param m - A `MonadRec` instance for `M`. 125 | * @param optionOp - An optional operation. 126 | * @returns The retrying operation. 127 | */ 128 | export const untilSome = 129 | (m: MonadRec) => (optionOp: Get1>): Get1 => 130 | m.tailRecM((_: never[]): Get1> => 131 | m.map( 132 | mapOrElse((): ControlFlow => newContinue([]))( 133 | newBreak, 134 | ), 135 | )(optionOp) 136 | )([]); 137 | -------------------------------------------------------------------------------- /src/type-class/monoid.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "../../deps.ts"; 2 | import { String } from "../../mod.ts"; 3 | import { fromIterable } from "../list.ts"; 4 | import { 5 | addMonoid, 6 | append, 7 | concat, 8 | flippedMonoid, 9 | maxMonoid, 10 | minMonoid, 11 | mulMonoid, 12 | trivialMonoid, 13 | } from "./monoid.ts"; 14 | 15 | Deno.test("append", () => { 16 | assertEquals(append(addMonoid)(2)(3), 5); 17 | }); 18 | Deno.test("concat", () => { 19 | assertEquals(concat(String.monoid)(fromIterable([])), ""); 20 | assertEquals(concat(String.monoid)(fromIterable(["go"])), "go"); 21 | assertEquals(concat(String.monoid)(fromIterable(["go", "to"])), "goto"); 22 | }); 23 | 24 | Deno.test("trivial monoid", () => { 25 | const m = trivialMonoid; 26 | // associative 27 | assertEquals( 28 | m.combine(m.combine([], []), []), 29 | m.combine([], m.combine([], [])), 30 | ); 31 | 32 | // identity 33 | assertEquals(m.combine([], m.identity), []); 34 | assertEquals(m.combine(m.identity, []), []); 35 | }); 36 | Deno.test("flipped monoid", () => { 37 | const m = flippedMonoid(String.monoid); 38 | 39 | // associative 40 | assertEquals( 41 | m.combine(m.combine("a", "b"), "c"), 42 | m.combine("a", m.combine("b", "c")), 43 | ); 44 | 45 | // identity 46 | for (const x of ["", "a", "bar"]) { 47 | assertEquals(m.combine(x, m.identity), x); 48 | assertEquals(m.combine(m.identity, x), x); 49 | } 50 | }); 51 | Deno.test("addition monoid", () => { 52 | const m = addMonoid; 53 | 54 | for (let x = -20; x <= 20; ++x) { 55 | for (let y = -20; y <= 20; ++y) { 56 | for (let z = -20; z <= 20; ++z) { 57 | assertEquals( 58 | m.combine(m.combine(x, y), z), 59 | m.combine(x, m.combine(y, z)), 60 | ); 61 | } 62 | } 63 | } 64 | 65 | for (let x = -100; x <= 100; ++x) { 66 | assertEquals(m.combine(x, m.identity), x); 67 | assertEquals(m.combine(m.identity, x), x); 68 | } 69 | }); 70 | Deno.test("multiplication monoid", () => { 71 | const m = mulMonoid; 72 | 73 | for (let x = -20; x <= 20; ++x) { 74 | for (let y = -20; y <= 20; ++y) { 75 | for (let z = -20; z <= 20; ++z) { 76 | assertEquals( 77 | m.combine(m.combine(x, y), z), 78 | m.combine(x, m.combine(y, z)), 79 | ); 80 | } 81 | } 82 | } 83 | 84 | for (let x = -100; x <= 100; ++x) { 85 | assertEquals(m.combine(x, m.identity), x); 86 | assertEquals(m.combine(m.identity, x), x); 87 | } 88 | }); 89 | Deno.test("minimum monoid", () => { 90 | const m = minMonoid(8001); 91 | 92 | for (let x = -20; x <= 20; ++x) { 93 | for (let y = -20; y <= 20; ++y) { 94 | for (let z = -20; z <= 20; ++z) { 95 | assertEquals( 96 | m.combine(m.combine(x, y), z), 97 | m.combine(x, m.combine(y, z)), 98 | ); 99 | } 100 | } 101 | } 102 | 103 | for (let x = -100; x <= 100; ++x) { 104 | assertEquals(m.combine(x, m.identity), x); 105 | assertEquals(m.combine(m.identity, x), x); 106 | } 107 | }); 108 | Deno.test("maximum monoid", () => { 109 | const m = maxMonoid(-8001); 110 | 111 | for (let x = -20; x <= 20; ++x) { 112 | for (let y = -20; y <= 20; ++y) { 113 | for (let z = -20; z <= 20; ++z) { 114 | assertEquals( 115 | m.combine(m.combine(x, y), z), 116 | m.combine(x, m.combine(y, z)), 117 | ); 118 | } 119 | } 120 | } 121 | 122 | for (let x = -100; x <= 100; ++x) { 123 | assertEquals(m.combine(x, m.identity), x); 124 | assertEquals(m.combine(m.identity, x), x); 125 | } 126 | }); 127 | -------------------------------------------------------------------------------- /src/type-class/monoid.ts: -------------------------------------------------------------------------------- 1 | import * as List from "../list.ts"; 2 | import { type SemiGroup, semiGroupSymbol } from "./semi-group.ts"; 3 | 4 | /** 5 | * All instances of `Monoid` must satisfy following conditions: 6 | * 7 | * - Associative: for all `x`, `y` and `z`; `combine(combine(x, y), z)` equals to `combine(x, combine(y, z))`. 8 | * - Identity: for all `x`; `combine(x, identity)` equals to `combine(identity, x)` and `x`. 9 | */ 10 | export type Monoid = SemiGroup & { 11 | readonly identity: T; 12 | }; 13 | 14 | export const append = (monoid: Monoid) => (l: T) => (r: T): T => 15 | monoid.combine(l, r); 16 | 17 | export const concat = (monoid: Monoid): (list: List.List) => T => 18 | List.foldL(append(monoid))(monoid.identity); 19 | 20 | export const trivialMonoid: Monoid = { 21 | combine: () => [], 22 | identity: [], 23 | [semiGroupSymbol]: true, 24 | }; 25 | 26 | export const flippedMonoid = (m: Monoid): Monoid => ({ 27 | ...m, 28 | combine: (l, r) => m.combine(r, l), 29 | }); 30 | 31 | export const addMonoid: Monoid = { 32 | combine(l: number, r: number): number { 33 | return l + r; 34 | }, 35 | identity: 0, 36 | [semiGroupSymbol]: true, 37 | }; 38 | 39 | export const mulMonoid: Monoid = { 40 | combine(l: number, r: number): number { 41 | return l * r; 42 | }, 43 | identity: 1, 44 | [semiGroupSymbol]: true, 45 | }; 46 | 47 | export const minMonoid = (infinity: number): Monoid => ({ 48 | combine(l, r): number { 49 | return Math.min(l, r); 50 | }, 51 | identity: infinity, 52 | [semiGroupSymbol]: true, 53 | }); 54 | 55 | export const maxMonoid = (negativeInfinity: number): Monoid => ({ 56 | combine(l, r): number { 57 | return Math.max(l, r); 58 | }, 59 | identity: negativeInfinity, 60 | [semiGroupSymbol]: true, 61 | }); 62 | -------------------------------------------------------------------------------- /src/type-class/monoidal.ts: -------------------------------------------------------------------------------- 1 | import type { GenericSemiGroupal } from "./semi-groupal.ts"; 2 | import type { Tensor } from "./tensor.ts"; 3 | import type { Unital } from "./unital.ts"; 4 | 5 | /** 6 | * All instance of `Monoidal` must satisfy: 7 | * 8 | * - Right unitality: For all `x`; `combine(genericRightMap(introduce)(x)) == backward(rightUnit)(forward(rightUnit)(x))`, 9 | * - Left unitality: For all `x`; `combine(genericLeftMap(introduce)(x)) == map(backward(leftUnit)(forward(leftUnit)(x)))`. 10 | */ 11 | export type Monoidal = 12 | & GenericSemiGroupal 13 | & Unital 14 | & { 15 | readonly tensor1: Tensor; 16 | readonly tensor2: Tensor; 17 | }; 18 | -------------------------------------------------------------------------------- /src/type-class/nt.ts: -------------------------------------------------------------------------------- 1 | import { id } from "../func.ts"; 2 | import type { Get1, Hkt2 } from "../hkt.ts"; 3 | import type { Category } from "./category.ts"; 4 | import type { Monoid } from "./monoid.ts"; 5 | import { type SemiGroup, semiGroupSymbol } from "./semi-group.ts"; 6 | 7 | export type NaturalTransformation = (f: Get1) => Get1; 8 | 9 | export type Nt = { 10 | readonly nt: NaturalTransformation; 11 | }; 12 | 13 | export interface NtHkt extends Hkt2 { 14 | readonly type: Nt; 15 | } 16 | 17 | export const category: Category = { 18 | identity: () => ({ nt: id }), 19 | compose: 20 | ({ nt: ntBC }: Nt) => 21 | ({ nt: ntAB }: Nt): Nt => ({ nt: (f) => ntBC(ntAB(f)) }), 22 | }; 23 | 24 | export const semiGroup = (): SemiGroup> => ({ 25 | combine: ({ nt: ntA }, { nt: ntB }) => ({ 26 | nt: (f) => ntB(ntA(f)), 27 | }), 28 | [semiGroupSymbol]: true, 29 | }); 30 | 31 | export const monoid = (): Monoid> => ({ 32 | combine: ({ nt: ntA }, { nt: ntB }) => ({ 33 | nt: (f) => ntB(ntA(f)), 34 | }), 35 | identity: { nt: id }, 36 | [semiGroupSymbol]: true, 37 | }); 38 | 39 | export type Transformation = { 40 | readonly transform: (t: T) => (fa: Get1) => Get1; 41 | }; 42 | 43 | export const transformation = (): Transformation> => ({ 44 | transform: ({ nt }) => nt, 45 | }); 46 | 47 | export const wrap = (nt: NaturalTransformation): Nt => ({ 48 | nt, 49 | }); 50 | 51 | export const unwrap = (t: Transformation) => t.transform; 52 | -------------------------------------------------------------------------------- /src/type-class/ord.ts: -------------------------------------------------------------------------------- 1 | import type { Get1, Hkt1 } from "../hkt.ts"; 2 | import { type Option, some } from "../option.ts"; 3 | import { isEq, type Ordering } from "../ordering.ts"; 4 | import type { HasNegInf } from "./has-neg-inf.ts"; 5 | import { type Eq, eqSymbol, stringEq } from "./eq.ts"; 6 | import type { HasInf } from "./has-inf.ts"; 7 | import type { PartialOrd } from "./partial-ord.ts"; 8 | import type { Contravariant } from "./variance.ts"; 9 | 10 | /** 11 | * All instances of `Ord` must satisfy following conditions: 12 | * - Transitivity: If `f` is `Ord`, for all `a`, `b` and `c`; `f(a, b) == f(b, c) == f(a, c)`. 13 | * - Duality: If `f` is `Ord`, for all `a` and `b`; `f(a, b) == -f(b, a)`. 14 | * - Strict: Ordering for all values is well-defined (so the return value is not an `Option`). 15 | */ 16 | export type Ord = PartialOrd & Eq & { 17 | readonly cmp: (lhs: Lhs, rhs: Rhs) => Ordering; 18 | }; 19 | 20 | export const stringOrd: Ord = { 21 | ...stringEq, 22 | cmp: (l, r) => l < r ? -1 : l === r ? 0 : 1, 23 | partialCmp: (l, r) => some(l < r ? -1 : l === r ? 0 : 1), 24 | }; 25 | 26 | export const fromCmp = 27 | (cmp: (x: X) => (lhs: Lhs, rhs: Rhs) => Ordering) => 28 | (x: X): Ord => ({ 29 | eq: (l, r) => isEq(cmp(x)(l, r)), 30 | partialCmp: (l, r) => some(cmp(x)(l, r)), 31 | cmp: cmp(x), 32 | [eqSymbol]: true, 33 | }); 34 | 35 | export const fromProjection = 36 | (projection: (structure: Get1) => X) => 37 | (order: Ord): Ord> => ({ 38 | eq: (l, r) => order.eq(projection(l), projection(r)), 39 | [eqSymbol]: true, 40 | partialCmp: (l, r) => order.partialCmp(projection(l), projection(r)), 41 | cmp: (l, r) => order.cmp(projection(l), projection(r)), 42 | }); 43 | 44 | export const reversed = (ord: Ord): Ord => ({ 45 | ...ord, 46 | cmp: (lhs, rhs) => -ord.cmp(lhs, rhs) as Ordering, 47 | partialCmp(lhs, rhs): Option { 48 | return some(-ord.cmp(lhs, rhs) as Ordering); 49 | }, 50 | }); 51 | 52 | export interface OrdHkt extends Hkt1 { 53 | readonly type: Ord; 54 | } 55 | 56 | export const contra: Contravariant = { 57 | contraMap: (f) => (ordB) => ({ 58 | cmp: (l, r) => ordB.cmp(f(l), f(r)), 59 | partialCmp: (l, r) => ordB.partialCmp(f(l), f(r)), 60 | eq: (l, r) => ordB.eq(f(l), f(r)), 61 | [eqSymbol]: true, 62 | }), 63 | }; 64 | 65 | export const nonNanOrd: 66 | & Ord 67 | & HasInf 68 | & HasNegInf = { 69 | ...fromCmp(() => (l: number, r: number) => { 70 | if (Number.isNaN(l) || Number.isNaN(r)) { 71 | throw new Error("NaN detected"); 72 | } 73 | return Math.sign(l - r) as Ordering; 74 | })(), 75 | infinity: Number.POSITIVE_INFINITY, 76 | negativeInfinity: Number.NEGATIVE_INFINITY, 77 | }; 78 | -------------------------------------------------------------------------------- /src/type-class/partial-eq.ts: -------------------------------------------------------------------------------- 1 | import type { Get1, Hkt1 } from "../hkt.ts"; 2 | import type { Monoid } from "./monoid.ts"; 3 | import { semiGroupSymbol } from "./semi-group.ts"; 4 | import type { Contravariant } from "./variance.ts"; 5 | 6 | /** 7 | * All instances of `PartialEq` must satisfy the following conditions: 8 | * - Symmetric: `PartialEq` always equals to `PartialEq`. 9 | * - Transitive: `PartialEq` and `PartialEq` always implies `PartialEq`. 10 | */ 11 | 12 | export type PartialEq = { 13 | readonly eq: (l: Lhs, r: Rhs) => boolean; 14 | }; 15 | 16 | export interface PartialEqHkt extends Hkt1 { 17 | readonly type: PartialEq; 18 | } 19 | 20 | export const contravariant: Contravariant = { 21 | contraMap: 22 | (f: (arg: T1) => T2) => (p: PartialEq): PartialEq => ({ 23 | eq: (l, r) => p.eq(f(l), f(r)), 24 | }), 25 | }; 26 | 27 | export const fromPartialEquality = ( 28 | partialEquality: (x: X) => (l: Lhs, r: Rhs) => boolean, 29 | ) => 30 | (x: X): PartialEq => ({ 31 | eq: partialEquality(x), 32 | }); 33 | 34 | export const fromProjection = 35 | (projection: (structure: Get1) => X) => 36 | ( 37 | equality: PartialEq, 38 | ): PartialEq, Get1> => ({ 39 | eq: (l, r) => equality.eq(projection(l), projection(r)), 40 | }); 41 | 42 | export const strict = (): PartialEq => 43 | fromPartialEquality(() => (l, r) => l === r)(); 44 | 45 | export const identity: () => PartialEq = 46 | fromPartialEquality(() => () => true); 47 | 48 | export const monoid = (): Monoid> => ({ 49 | combine: (x, y) => ({ eq: (l, r) => x.eq(l, r) && y.eq(l, r) }), 50 | identity: identity(), 51 | [semiGroupSymbol]: true, 52 | }); 53 | 54 | export type PartialEqUnary = { 55 | readonly liftEq: ( 56 | equality: (l: Lhs, r: Rhs) => boolean, 57 | ) => (l: Get1, r: Get1) => boolean; 58 | }; 59 | 60 | /** 61 | * Creates the new lifter of equality from a transformer of `PartialEq`. 62 | * 63 | * @param lifter - The function transforming about `PartialEq`. 64 | * @returns The new `PartialEqUnary` for `F`. 65 | */ 66 | export const fromLifter = ( 67 | lifter: ( 68 | eq: PartialEq, 69 | ) => PartialEq, Get1>, 70 | ): PartialEqUnary => ({ 71 | liftEq: (equality) => (l, r) => lifter({ eq: equality }).eq(l, r), 72 | }); 73 | -------------------------------------------------------------------------------- /src/type-class/partial-ord.ts: -------------------------------------------------------------------------------- 1 | import type { Get1, Hkt1 } from "../hkt.ts"; 2 | import { flatMap, map, mapOr, type Option, some } from "../option.ts"; 3 | import { and, equal, isEq, type Ordering } from "../ordering.ts"; 4 | import type { Monoid } from "./monoid.ts"; 5 | import type { PartialEq } from "./partial-eq.ts"; 6 | import { semiGroupSymbol } from "./semi-group.ts"; 7 | import type { Contravariant } from "./variance.ts"; 8 | 9 | /** 10 | * All instances of `PartialOrd` must satisfy following conditions: 11 | * - Transitivity: If `f` is `PartialOrd`, for all `a`, `b` and `c`; `f(a, b) == f(b, c) == f(a, c)`. 12 | * - Duality: If `f` is `PartialOrd`, for all `a` and `b`; `f(a, b) == -f(b, a)`. 13 | */ 14 | export type PartialOrd = PartialEq & { 15 | readonly partialCmp: (lhs: Lhs, rhs: Rhs) => Option; 16 | }; 17 | 18 | export const fromPartialCmp = ( 19 | partialCmp: (x: X) => (lhs: Lhs, rhs: Rhs) => Option, 20 | ) => 21 | (x: X): PartialOrd => ({ 22 | partialCmp: partialCmp(x), 23 | eq: (l, r) => 24 | mapOr(false)((order: Ordering) => isEq(order))(partialCmp(x)(l, r)), 25 | }); 26 | 27 | export const fromProjection = 28 | (projection: (structure: Get1) => X) => 29 | (order: PartialOrd): PartialOrd> => ({ 30 | eq: (l, r) => order.eq(projection(l), projection(r)), 31 | partialCmp: (l, r) => order.partialCmp(projection(l), projection(r)), 32 | }); 33 | 34 | export interface PartialOrdHkt extends Hkt1 { 35 | readonly type: PartialOrd; 36 | } 37 | 38 | export const contravariant: Contravariant = { 39 | contraMap: (f: (t1: T1) => U1) => (ord: PartialOrd) => 40 | fromPartialCmp(() => (l: T1, r: T1) => ord.partialCmp(f(l), f(r)))(), 41 | }; 42 | 43 | export const identity: PartialOrd = fromPartialCmp(() => () => 44 | some(equal) 45 | )(); 46 | 47 | export const monoid = (): Monoid> => ({ 48 | combine: (x, y) => ({ 49 | partialCmp: (l, r) => 50 | flatMap((first: Ordering) => 51 | map((second: Ordering) => and(second)(first))( 52 | y.partialCmp(l, r), 53 | ) 54 | )(x.partialCmp(l, r)), 55 | eq: (l, r) => x.eq(l, r) && y.eq(l, r), 56 | }), 57 | identity, 58 | [semiGroupSymbol]: true, 59 | }); 60 | -------------------------------------------------------------------------------- /src/type-class/profunctor.ts: -------------------------------------------------------------------------------- 1 | import { type FnHkt, id } from "../func.ts"; 2 | import type { Get2 } from "../hkt.ts"; 3 | 4 | export type Profunctor = { 5 | readonly diMap: ( 6 | f: (a: A) => B, 7 | ) => (g: (c: C) => D) => (m: Get2) => Get2; 8 | }; 9 | 10 | export const leftMap = 11 | (pro: Profunctor) => 12 | (f: (a: A) => B): (m: Get2) => Get2 => 13 | pro.diMap(f)(id); 14 | export const rightMap = ( 15 | pro: Profunctor, 16 | ): (f: (a: C) => D) => (m: Get2) => Get2 => 17 | pro.diMap(id); 18 | 19 | export const fnPro: Profunctor = { 20 | diMap: (f) => (g) => (m) => (a) => g(m(f(a))), 21 | }; 22 | -------------------------------------------------------------------------------- /src/type-class/pure.ts: -------------------------------------------------------------------------------- 1 | import type { Get1 } from "../hkt.ts"; 2 | 3 | export type Pure = { 4 | readonly pure: (t: T) => Get1; 5 | }; 6 | 7 | export const when = 8 | (app: Pure) => 9 | (cond: boolean) => 10 | (op: Get1): Get1 => cond ? op : app.pure([]); 11 | 12 | export const unless = 13 | (app: Pure) => 14 | (cond: boolean) => 15 | (op: Get1): Get1 => cond ? app.pure([]) : op; 16 | -------------------------------------------------------------------------------- /src/type-class/reduce.ts: -------------------------------------------------------------------------------- 1 | import type { Get1 } from "../hkt.ts"; 2 | 3 | export type Reduce = { 4 | readonly reduceR: ( 5 | reducer: (a: A) => (b: B) => B, 6 | ) => (fa: Get1) => (b: B) => B; 7 | readonly reduceL: ( 8 | reducer: (b: B) => (a: A) => B, 9 | ) => (b: B) => (fa: Get1) => B; 10 | }; 11 | -------------------------------------------------------------------------------- /src/type-class/representable.ts: -------------------------------------------------------------------------------- 1 | import type { Get1, Get2, Hkt0, Hkt1 } from "../hkt.ts"; 2 | import type { Functor } from "./functor.ts"; 3 | import type { Strong } from "./strong.ts"; 4 | 5 | /** 6 | * An HKT extension to provide associated type `Rep` for `Representable`. 7 | */ 8 | export interface HktRep { 9 | readonly rep: Hkt1; // Representation type 10 | } 11 | 12 | export type ApplyRep = P extends Hkt0 ? P & { readonly rep: H } : never; 13 | export type Rep

= P extends HktRep ? P["rep"] : never; 14 | export type GetRep = Get1, T>; 15 | 16 | /** 17 | * `F` should extend `HktRep` because functions below require `HktRep`. 18 | * 19 | * All instances of `Representable` must satisfy these laws: 20 | * 21 | * - `compose(index)(tabulate) == id`, 22 | * - `compose(tabulate)(index) == id`. 23 | */ 24 | export type Representable

= Strong

& { 25 | readonly functor: Functor>; 26 | readonly index: (f: Get2) => (rep: T) => GetRep; 27 | readonly tabulate: (f: (rep: T) => GetRep) => Get2; 28 | }; 29 | 30 | export const first = 31 |

(p: Representable

) => 32 | (pab: Get2): Get2 => 33 | p.tabulate(([a, c]) => 34 | p.functor.map((b: B) => [b, c])(p.index(pab)(a)) 35 | ); 36 | 37 | export const second = 38 |

(p: Representable

) => 39 | (pab: Get2): Get2 => 40 | p.tabulate(([c, a]) => 41 | p.functor.map((b: B) => [c, b])(p.index(pab)(a)) 42 | ); 43 | -------------------------------------------------------------------------------- /src/type-class/reviewable.ts: -------------------------------------------------------------------------------- 1 | import type { Bifunctor } from "./bifunctor.ts"; 2 | import type { Profunctor } from "./profunctor.ts"; 3 | 4 | export type Reviewable

= Profunctor

& Bifunctor

; 5 | -------------------------------------------------------------------------------- /src/type-class/ring.ts: -------------------------------------------------------------------------------- 1 | import { type AbelianGroup, trivialAbelianGroup } from "./abelian-group.ts"; 2 | import { type Monoid, trivialMonoid } from "./monoid.ts"; 3 | 4 | /** 5 | * A structure which can operate addition, subtraction and multiplication. 6 | * 7 | * All instance of `Ring` must satisfy following conditions: 8 | * 9 | * - Associative on addition: for all `x`, `y` and `z`; `additive.combine(additive.combine(x, y), z)` equals to `additive.combine(x, additive.combine(y, z))` 10 | * - Identity on addition: for all `x`; `additive.combine(additive.identity, x)` equals to `x`. 11 | * - Inverse on addition: for all `x`; exists `y`; `additive.combine(x, y)` equals to `additive.identity`. 12 | * - Commutative on addition: for all `x` and `y`; `additive.combine(x, y)` equals to `additive.combine(y, x)`. 13 | * - Associative on multiplication: for all `x`, `y` and `z`; `multiplication.combine(multiplication.combine(x, y), z)` equals to `multiplication.combine(x, multiplication.combine(y, z))` 14 | * - Identity on multiplication: for all `x`; `multiplication.combine(multiplication.identity, x)` equals to `multiplication.combine(x, multiplication.identity)` and `x`. 15 | * - Distributive: for all `x`, `y` and `z`; `multiplication.combine(x, additive.combine(y, z))` equals to `additive.combine(multiplication.combine(x, y), multiplication.combine(x, z))` 16 | */ 17 | export type Ring = { 18 | readonly additive: AbelianGroup; 19 | readonly multiplication: Monoid; 20 | }; 21 | 22 | export const trivialRing: Ring = { 23 | additive: trivialAbelianGroup, 24 | multiplication: trivialMonoid, 25 | }; 26 | -------------------------------------------------------------------------------- /src/type-class/semi-group.ts: -------------------------------------------------------------------------------- 1 | import type { Magma } from "./magma.ts"; 2 | 3 | export const semiGroupSymbol = Symbol("ImplSemiGroup"); 4 | 5 | /** 6 | * Associative 2-term operation. All instances of `SemiGroup` must satisfy following conditions: 7 | * 8 | * - Associative: If `S` is a `SemiGroup`, for all `x`, `y` and `z`, `S.combine(S.combine(x, y), z)` equals to `S.combine(x, S.combine(y, z))`. 9 | */ 10 | export type SemiGroup = Magma & { 11 | [semiGroupSymbol]: true; 12 | }; 13 | 14 | export const combineAll = 15 | (s: SemiGroup) => (init: T) => (arr: readonly T[]): T => 16 | arr.reduce((elem, acc) => s.combine(elem, acc), init); 17 | -------------------------------------------------------------------------------- /src/type-class/semi-groupal.ts: -------------------------------------------------------------------------------- 1 | import type { Get1, Get2 } from "../hkt.ts"; 2 | import type { Tuple } from "../tuple.ts"; 3 | import type { Associative } from "./associative.ts"; 4 | 5 | export type SemiGroupal = { 6 | readonly product: ( 7 | fa: Get1, 8 | ) => (fb: Get1) => Get1>; 9 | }; 10 | 11 | /** 12 | * All instance of `GenericSemiGroupal` must satisfy: 13 | * 14 | * - For all `x`; `combine(genericRightMap(combine)(backward(assoc)(x))) == map(backward(assoc))(combine(genericLeftMap(combine)(x)))`. 15 | */ 16 | export type GenericSemiGroupal = { 17 | readonly assoc1: Associative; 18 | readonly assoc2: Associative; 19 | readonly combine: () => Get2< 20 | Cat, 21 | Get2, Get1>, 22 | Get1> 23 | >; 24 | }; 25 | -------------------------------------------------------------------------------- /src/type-class/semi-groupoid.ts: -------------------------------------------------------------------------------- 1 | import type { Get2 } from "../hkt.ts"; 2 | 3 | /** 4 | * A 2-arity kind which can compose two relationships. There is no required laws. 5 | */ 6 | export type SemiGroupoid = { 7 | /** 8 | * Composes two relationships into a new one. 9 | * 10 | * @param funcA - A relationship from `B` to `C`. 11 | * @param funcB - A relationship from `A` to `B`. 12 | * @returns The new relation ship from `A` to `C`. 13 | */ 14 | readonly compose: ( 15 | funcA: Get2, 16 | ) => (funcB: Get2) => Get2; 17 | }; 18 | -------------------------------------------------------------------------------- /src/type-class/semi-ring.ts: -------------------------------------------------------------------------------- 1 | import type { AbelianMonoid } from "./abelian-monoid.ts"; 2 | import type { Monoid } from "./monoid.ts"; 3 | 4 | /** 5 | * A `SemiRing` instance for `T` must satisfy these laws: 6 | * 7 | * - Additive is an abelian monoid. The identity of `additive` is called `zero`. 8 | * - Multiplication is a monoid. The identity of `multiplication` is called `one`. 9 | * - On multiplication, any element `x` is left and right annihilated by `zero`: 10 | * - `multiplication.combine(zero, x)` = `zero`, 11 | * - `multiplication.combine(x, zero)` = `zero`. 12 | */ 13 | export type SemiRing = { 14 | additive: AbelianMonoid; 15 | multiplication: Monoid; 16 | }; 17 | -------------------------------------------------------------------------------- /src/type-class/settable.ts: -------------------------------------------------------------------------------- 1 | import type { Get1, Get2 } from "../hkt.ts"; 2 | import type { Applicative } from "./applicative.ts"; 3 | import type { Distributive } from "./distributive.ts"; 4 | import { type Profunctor, rightMap } from "./profunctor.ts"; 5 | import type { Traversable } from "./traversable.ts"; 6 | 7 | export type Settable = Applicative & Distributive & Traversable & { 8 | readonly untainted: (fa: Get1) => A; 9 | }; 10 | 11 | export const untaintedDot = 12 | (settable: Settable) => 13 |

(pro: Profunctor

) => 14 | (g: Get2>): Get2 => 15 | rightMap(pro), B>(settable.untainted)(g); 16 | 17 | export const taintedDot = 18 | (settable: Settable) => 19 |

(pro: Profunctor

) => 20 | (g: Get2): Get2> => 21 | rightMap(pro)>(settable.pure)(g); 22 | -------------------------------------------------------------------------------- /src/type-class/strong.ts: -------------------------------------------------------------------------------- 1 | import type { Get2 } from "../hkt.ts"; 2 | import type { Category } from "./category.ts"; 3 | import { type Profunctor, rightMap } from "./profunctor.ts"; 4 | 5 | export type Strong = Profunctor & { 6 | readonly first: (m: Get2) => Get2; 7 | readonly second: (m: Get2) => Get2; 8 | }; 9 | 10 | export const split = 11 | (str: Strong, cat: Category) => 12 | (funcA: Get2) => 13 | (funcC: Get2): Get2 => 14 | cat.compose(str.first(funcA))<[A, C]>(str.second(funcC)); 15 | 16 | export const fanOut = (str: Strong, cat: Category) => 17 | ( 18 | funcB: Get2, 19 | ): (funcC: Get2) => Get2 => { 20 | const splitSC: (func: Get2) => Get2 = 21 | split( 22 | str, 23 | cat, 24 | )(funcB); 25 | return (funcC: Get2): Get2 => 26 | cat.compose(splitSC(funcC))( 27 | rightMap(str)((a: A): [A, A] => [a, a])(cat.identity()), 28 | ); 29 | }; 30 | 31 | export type Costrong = { 32 | readonly unFirst: (m: Get2) => Get2; 33 | readonly unSecond: (m: Get2) => Get2; 34 | }; 35 | -------------------------------------------------------------------------------- /src/type-class/symmetric.ts: -------------------------------------------------------------------------------- 1 | import type { Get2 } from "../hkt.ts"; 2 | import type { Associative } from "./associative.ts"; 3 | 4 | export type Symmetric = Associative & { 5 | readonly swap: () => Get2, Get2>; 6 | }; 7 | -------------------------------------------------------------------------------- /src/type-class/tensor.ts: -------------------------------------------------------------------------------- 1 | import type { Get2 } from "../hkt.ts"; 2 | import type { Associative } from "./associative.ts"; 3 | import type { Iso } from "./iso.ts"; 4 | 5 | /** 6 | * All instance of `Tensor` must satisfy: 7 | * 8 | * - `forward(leftUnit())(a.compose(i)) == a`, 9 | * - `backward(leftUnit())(a) == a.compose(i)`, 10 | * - `forward(rightUnit())(i.compose(a)) == a`, 11 | * - `backward(rightUnit())(a) == i.compose(a)`. 12 | */ 13 | export type Tensor = Associative & { 14 | readonly leftUnit: () => Iso, A>; 15 | readonly rightUnit: () => Iso, A>; 16 | }; 17 | -------------------------------------------------------------------------------- /src/type-class/traversable-monad.ts: -------------------------------------------------------------------------------- 1 | import type { Monad } from "./monad.ts"; 2 | import type { Traversable } from "./traversable.ts"; 3 | 4 | export type TraversableMonad = Traversable & Monad; 5 | -------------------------------------------------------------------------------- /src/type-class/unital.ts: -------------------------------------------------------------------------------- 1 | import type { Get1, Get2 } from "../hkt.ts"; 2 | 3 | export type Unital = { 4 | readonly introduce: Get2>; 5 | }; 6 | -------------------------------------------------------------------------------- /src/type-class/variance.ts: -------------------------------------------------------------------------------- 1 | import { constant } from "../func.ts"; 2 | import type { Get1 } from "../hkt.ts"; 3 | import { type Functor, replace as replaceFunctor } from "./functor.ts"; 4 | 5 | export type Invariant = { 6 | readonly inMap: ( 7 | f: (t1: T1) => U1, 8 | ) => (g: (u1: U1) => T1) => (st: Get1) => Get1; 9 | }; 10 | 11 | export type Contravariant = { 12 | readonly contraMap: ( 13 | f: (arg: T1) => U1, 14 | ) => (u: Get1) => Get1; 15 | }; 16 | 17 | export const replace = 18 | (contra: Contravariant) => 19 | (b: B): (fb: Get1) => Get1 => 20 | contra.contraMap(constant(b)); 21 | 22 | export const replaceFlipped = 23 | (contra: Contravariant) => 24 | (fb: Get1) => 25 | (b: B): Get1 => contra.contraMap(constant(b))(fb); 26 | 27 | export const phantom = 28 | (functor: Functor, contra: Contravariant) => 29 | (fa: Get1): Get1 => 30 | replaceFlipped(contra)(replaceFunctor(functor)([])(fa))([]); 31 | 32 | export const contraAsIn = (contra: Contravariant): Invariant => ({ 33 | inMap: () => contra.contraMap, 34 | }); 35 | -------------------------------------------------------------------------------- /src/writer.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "../deps.ts"; 2 | import { cat, doVoidT } from "./cat.ts"; 3 | import type { Monoid } from "./type-class/monoid.ts"; 4 | import { semiGroupSymbol } from "./type-class/semi-group.ts"; 5 | import { 6 | censor, 7 | evaluateWriter, 8 | executeWriter, 9 | flatMap, 10 | listen, 11 | makeMonad, 12 | map, 13 | pure, 14 | tell, 15 | type Writer, 16 | } from "./writer.ts"; 17 | 18 | const monoidArray = (): Monoid => ({ 19 | identity: [], 20 | combine: (l, r) => [...l, ...r], 21 | [semiGroupSymbol]: true, 22 | }); 23 | 24 | Deno.test("tell with tower of hanoi", () => { 25 | const monoid = monoidArray<[string, string]>(); 26 | 27 | const hanoi = ( 28 | height: number, 29 | from: string, 30 | to: string, 31 | another: string, 32 | ): Writer<[string, string][], never[]> => { 33 | if (height < 1) { 34 | return pure(monoid)([]); 35 | } 36 | if (height === 1) { 37 | return tell([[from, to]]); 38 | } 39 | return cat(hanoi(height - 1, from, another, to)) 40 | .feed(flatMap(monoid)(() => tell([[from, to]]))) 41 | .feed(flatMap(monoid)(() => hanoi(height - 1, another, to, from))) 42 | .value; 43 | }; 44 | 45 | const res = hanoi(3, "A", "B", "C"); 46 | assertEquals(executeWriter(res), [ 47 | ["A", "B"], 48 | ["A", "C"], 49 | ["B", "C"], 50 | ["A", "B"], 51 | ["C", "A"], 52 | ["C", "B"], 53 | ["A", "B"], 54 | ]); 55 | }); 56 | 57 | Deno.test("listen with collatz sequence", () => { 58 | const monoid = monoidArray(); 59 | 60 | const collatz = (n: number) => { 61 | if (n % 2 === 0) { 62 | return n / 2; 63 | } 64 | return 3 * n + 1; 65 | }; 66 | const collatzW = (n: number): Writer => 67 | cat(tell([n])).feed(map(() => collatz(n))).value; 68 | const lengthOfSeq = ( 69 | writer: Writer, 70 | ): Writer => 71 | map(([_last, numbers]: [number, number[]]) => numbers.length)( 72 | listen(writer), 73 | ); 74 | const collatzSeq = (n: number): Writer => { 75 | const seq = (num: number): Writer => 76 | cat(collatzW(num)).feed( 77 | flatMap(monoid)((value: number) => { 78 | if (value === 1) { 79 | return pure(monoid)(1); 80 | } 81 | return seq(value); 82 | }), 83 | ).value; 84 | return lengthOfSeq(seq(n)); 85 | }; 86 | 87 | const res = collatzSeq(13); 88 | assertEquals(executeWriter(res), [13, 40, 20, 10, 5, 16, 8, 4, 2]); 89 | assertEquals(evaluateWriter(res), 9); 90 | }); 91 | 92 | Deno.test("censor with log decoration", () => { 93 | const m = makeMonad(monoidArray()); 94 | 95 | const hello = doVoidT(m) 96 | .run(tell(["Hello!"])) 97 | .run(tell(["What do you do?"])).ctx; 98 | const log = censor((messages: string[]) => 99 | messages.map((message) => `[LOG] ${message}`) 100 | )( 101 | hello, 102 | ); 103 | assertEquals(executeWriter(log), [ 104 | "[LOG] Hello!", 105 | "[LOG] What do you do?", 106 | ]); 107 | }); 108 | -------------------------------------------------------------------------------- /src/writer/monad.ts: -------------------------------------------------------------------------------- 1 | import type { Get1 } from "../hkt.ts"; 2 | import type { Monad } from "../type-class/monad.ts"; 3 | import type { Monoid } from "../type-class/monoid.ts"; 4 | 5 | export type MonadWriter = Monoid & Monad & { 6 | readonly tell: (output: W) => Get1; 7 | readonly listen: (action: Get1) => Get1; 8 | readonly pass: (action: Get1 W]>) => Get1; 9 | }; 10 | 11 | export const writer = 12 | (mw: MonadWriter) => ([a, w]: readonly [A, W]): Get1 => 13 | mw.flatMap(() => mw.pure(a))(mw.tell(w)); 14 | 15 | export const listens = 16 | (mw: MonadWriter) => 17 | (f: (w: W) => B) => 18 | (m: Get1): Get1 => 19 | mw.map(([action, output]: [A, W]): [A, B] => [action, f(output)])( 20 | mw.listen(m), 21 | ); 22 | export const censor = 23 | (mw: MonadWriter) => 24 | (f: (w: W) => W) => 25 | (m: Get1): Get1 => 26 | mw.pass(mw.map((a: A): [A, typeof f] => [a, f])(m)); 27 | -------------------------------------------------------------------------------- /src/yoneda.ts: -------------------------------------------------------------------------------- 1 | import { compose, id } from "./func.ts"; 2 | import type { Apply2Only, Get1, Hkt2 } from "./hkt.ts"; 3 | import type { Functor } from "./type-class/functor.ts"; 4 | import type { PartialEqUnary } from "./type-class/partial-eq.ts"; 5 | 6 | /** 7 | * The yoneda functor, a partial application of `map` to its second argument. 8 | */ 9 | export type Yoneda = { 10 | readonly yoneda: (fn: (a: A) => X) => Get1; 11 | }; 12 | 13 | /** 14 | * The `PartialEqUnary` instance for `Yoneda`. 15 | */ 16 | export const partialEqUnary = ( 17 | eqUnary: PartialEqUnary, 18 | ): PartialEqUnary => ({ 19 | liftEq: 20 | (equality: (l: L, r: R) => boolean) => 21 | (l: Yoneda, r: Yoneda): boolean => 22 | eqUnary.liftEq(equality)(l.yoneda(id), r.yoneda(id)), 23 | }); 24 | 25 | /** 26 | * Lifts the value `a` into a partial application of `functor.map`. It is the natural isomorphism to `Yoneda`. 27 | * 28 | * @param functor - The instance of `Functor` for `F`. 29 | * @param a - The value in `F`. 30 | * @returns The partial application of `functor.map`. 31 | */ 32 | export const lift = 33 | (functor: Functor) => (a: Get1): Yoneda => ({ 34 | yoneda: (f) => functor.map(f)(a), 35 | }); 36 | 37 | /** 38 | * Lowers the yoneda into the value in `F`. It is the natural isomorphism from `Yoneda`. 39 | * 40 | * @param y - The yoneda to be lowered. 41 | * @returns The inner value in `F`. 42 | */ 43 | export const lower = (y: Yoneda): Get1 => y.yoneda(id); 44 | 45 | /** 46 | * Maps the yoneda with `mapper`. 47 | * 48 | * @param mapper - The function to map from `A`. 49 | * @param y - The yoneda to be mapped. 50 | * @returns The mapped yoneda 51 | */ 52 | export const map = 53 | (mapper: (a: A) => B) => (y: Yoneda): Yoneda => ({ 54 | yoneda: (fn: (b: B) => X) => y.yoneda(compose(fn)(mapper)), 55 | }); 56 | 57 | export interface YonedaHkt extends Hkt2 { 58 | readonly type: Yoneda; 59 | } 60 | 61 | /** 62 | * The instance of `Functor` for `Yoneda`. 63 | */ 64 | export const functor = (): Functor> => ({ map }); 65 | -------------------------------------------------------------------------------- /src/zipper.test.ts: -------------------------------------------------------------------------------- 1 | import { range } from "./list.ts"; 2 | import { none, some, unwrap } from "./option.ts"; 3 | import { end, fromList, left, right, start, top } from "./zipper.ts"; 4 | import { assertEquals } from "../deps.ts"; 5 | 6 | Deno.test("seeking", () => { 7 | const zipper = unwrap(fromList(range(0, 8))); 8 | assertEquals(zipper.current, 0); 9 | assertEquals(top(zipper), [none(), 0, some(1)]); 10 | 11 | const next = unwrap(right(zipper)); 12 | assertEquals(top(next), [some(0), 1, some(2)]); 13 | const back = unwrap(left(next)); 14 | assertEquals(top(back), [none(), 0, some(1)]); 15 | 16 | const endOfList = end(back); 17 | assertEquals(top(endOfList), [some(6), 7, none()]); 18 | const startOfList = start(endOfList); 19 | assertEquals(top(startOfList), [none(), 0, some(1)]); 20 | }); 21 | -------------------------------------------------------------------------------- /tsconfig.doc.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "skipLibCheck": true, 5 | "strict": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "module": "EsNext", 8 | "moduleResolution": "NodeNext", 9 | "resolveJsonModule": true, 10 | "isolatedModules": true, 11 | "noImplicitAny": true, 12 | "noImplicitReturns": true, 13 | "noEmit": true, 14 | "verbatimModuleSyntax": true, 15 | "baseUrl": "." 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "lib": ["deno.ns"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "esModuleInterop": true, 10 | "module": "NodeNext", 11 | "moduleResolution": "NodeNext", 12 | "moduleDetection": "force", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noImplicitAny": true, 16 | "noImplicitReturns": true, 17 | "noImplicitThis": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedIndexedAccess": true, 22 | "useUnknownInCatchVariables": true, 23 | "exactOptionalPropertyTypes": true, 24 | "declaration": true, 25 | "emitDeclarationOnly": true, 26 | "verbatimModuleSyntax": true, 27 | "outDir": "./dist", 28 | "declarationDir": "./dist", 29 | "baseUrl": "." 30 | }, 31 | "exclude": ["npm"] 32 | } 33 | --------------------------------------------------------------------------------