├── .github └── workflows │ └── deno.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── TODO.md ├── affect.ts ├── array.ts ├── const.ts ├── datum.ts ├── derivations.ts ├── either.ts ├── examples ├── array │ ├── ap.ts │ └── traverse.ts ├── do.ts ├── either │ └── tryCatch.ts ├── fetch_archives.ts ├── optics.ts ├── schemable.ts └── semigroup.ts ├── fns.ts ├── hkt.ts ├── identity.ts ├── io.ts ├── io_either.ts ├── map.ts ├── monoid.ts ├── nilable.ts ├── optics ├── at.ts ├── from_traversable.ts ├── index.ts ├── iso.ts ├── lens.ts ├── optional.ts ├── prism.ts └── traversal.ts ├── option.ts ├── ord.ts ├── reader.ts ├── reader_either.ts ├── record.ts ├── schemable ├── decode_error.ts ├── decoder.ts ├── guard.ts └── schemable.ts ├── semigroup.ts ├── sequence.ts ├── set.ts ├── setoid.ts ├── state.ts ├── task.ts ├── task_either.ts ├── testing ├── affect.test.ts ├── array.test.ts ├── assert.ts ├── const.test.ts ├── datum.test.ts ├── either.test.ts ├── fns.test.ts ├── identity.test.ts ├── io.test.ts ├── io_either.test.ts ├── map.test.ts ├── monoid.test.ts ├── nilable.test.ts ├── optics │ ├── at.test.ts │ ├── from_traversable.test.ts │ ├── index.test.ts │ ├── iso.test.ts │ ├── lens.test.ts │ ├── optional.test.ts │ ├── prism.test.ts │ └── traversal.test.ts ├── option.test.ts ├── ord.test.ts ├── reader.test.ts ├── reader_either.test.ts ├── record.test.ts ├── schemable │ ├── decode_error.test.ts │ ├── decoder.test.ts │ └── guard.test.ts ├── semigroup.test.ts ├── set.test.ts ├── setoid.test.ts ├── state.test.ts ├── task.test.ts ├── task_either.test.ts ├── these.test.ts └── tree.test.ts ├── these.ts ├── tree.ts ├── type_classes.ts └── types.ts /.github/workflows/deno.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | # This workflow will install Deno and run tests across stable and nightly builds on Windows, Ubuntu and macOS. 7 | # For more information see: https://github.com/denolib/setup-deno 8 | 9 | name: Deno 10 | 11 | on: 12 | push: 13 | branches: [main] 14 | pull_request: 15 | branches: [main] 16 | 17 | jobs: 18 | test: 19 | runs-on: ${{ matrix.os }} # runs a test on Ubuntu, Windows and macOS 20 | 21 | strategy: 22 | matrix: 23 | deno: ["v1.x"] 24 | os: [macOS-latest, windows-latest, ubuntu-latest] 25 | 26 | steps: 27 | - name: Setup repo 28 | uses: actions/checkout@v2 29 | 30 | - name: Setup Deno 31 | uses: denolib/setup-deno@v2.3.0 32 | with: 33 | deno-version: ${{ matrix.deno }} 34 | 35 | - name: Run Deno Tests 36 | run: deno test -A --unstable --coverage=coverage 37 | 38 | - name: Generate Coverage 39 | run: deno coverage --unstable ./coverage --lcov > ./coverage.lcov 40 | 41 | - name: Collect coverage 42 | uses: codecov/codecov-action@v1.0.10 43 | with: 44 | file: ./coverage.lcov 45 | 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .vim-lsp-settings 3 | draft 4 | *.swp 5 | coverage 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Giulio Canti 4 | Copyright (c) 2018 Tom Crockett 5 | Copyright (c) 2019 Brandon Blaylock 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DEPRECATED 2 | 3 | This repository has been replaced by [functional](https://github.com/nullpub/fun)! 4 | 5 | It will be kept alive as a historical reference and as a deno target in the 6 | event that someone is relying on its existence. 7 | 8 | # hkts ![Deno](https://github.com/nullpub/hkts/workflows/Deno/badge.svg?branch=master) 9 | 10 | Higher kinded types for [Deno](https://deno.land). As an avid user of 11 | [fp-ts](https://github.com/gcanti/fp-ts) I wanted to have a similarly full 12 | featured environment in Deno. Unfortunately, the fp-ts port to Deno is clunky to 13 | use with other functional libraries like 14 | [@nll/datum](https://github.com/nullpub/datum), 15 | [io-ts](https://github.com/gcanti/io-ts), and 16 | [monocle-ts](https://github.com/gcanti/monocle-ts). While I could have ported 17 | fp-ts directly, I've come to like the exploratory work done by pelotom in the 18 | original [hkts](http://github.com/pelotom/hkts). Thus, I've decided to port the 19 | functionality of fp-ts, io-ts, and monocle-ts to Deno using the HKT substitution 20 | developed by pelotom. 21 | 22 | This library is primarily an exercise, but I intend to maintain 100% test 23 | coverage. Instead of breaking out the clones of io-ts, monocle-ts, and datum 24 | into other Deno modules they will all be landed here. There will be no barrel 25 | exports as importing in Deno is much cleaner without them. Contributions are 26 | welcome. 27 | 28 | ## Installation 29 | 30 | This library is meant to be used with Deno, thus it follows the 31 | [Deno imports](https://deno.land/manual/examples/import_export) syntax. 32 | 33 | ## Conventions 34 | 35 | This library focuses first on implementing 36 | [static-land](https://github.com/fantasyland/static-land) type classes for a 37 | given Algebraic Data Type (ie. Either or Option). These type class modules are 38 | then exported from the ADT's namespace (eg. 39 | `import { Monad } from 'https://deno.land/x/hkts/option.ts'`). 40 | 41 | With the exception of instance constructors (ie. getShow or getSemigroup) other 42 | ADT functions should all be pipeable. For functions that derive from type class 43 | modules, like `chain` or `map`, there are helpers in `derivations.ts` that will 44 | generate the pipeable versions for you. 45 | 46 | For good examples of the above conventions look at the `either.ts` or 47 | `option.ts`. 48 | 49 | # Documentation 50 | 51 | For the foreseeable future this library will not focus on documentation. 52 | Questions are welcome via 53 | [github issues](https://github.com/nullpub/hkts/issues) but I can't guaruntee 54 | speedy responses. Once a decent collection of ADTs and other utilities are 55 | ported and all the pre-1.0.0 todo items in `TODO.md` are complete I'll shift to 56 | documentation. Even then it's likely that I'll auto-generate the raw docs from 57 | exported function and statement types and will devote any time to building an 58 | example library that doubles as extra tests. 59 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # Initial Push Todos 2 | 3 | - implement tests for other algebraic modules 4 | - need Traversal and ChainRec still 5 | - look at TODO items across codebase 6 | - curry semigroup and anything else that should be curried 7 | - work through adt by adt to add any useful missing modules 8 | - show 9 | - semigroup 10 | - ord 11 | - etc 12 | - stack safe recursion for sequence constructors 13 | - trampoline? 14 | - auto documentation (port docs-ts?) 15 | - just use deno docs 16 | - spend a week on examples and introduction posts 17 | - work with tmueller? 18 | - consider migrating from const arrows to functions 19 | - TESTS! 20 | -------------------------------------------------------------------------------- /affect.ts: -------------------------------------------------------------------------------- 1 | import type * as TC from "./type_classes.ts"; 2 | import type * as HKT from "./hkt.ts"; 3 | 4 | import * as E from "./either.ts"; 5 | import { createDo } from "./derivations.ts"; 6 | import { flow, identity, pipe } from "./fns.ts"; 7 | import { createSequenceStruct, createSequenceTuple } from "./sequence.ts"; 8 | 9 | /******************************************************************************* 10 | * Types 11 | ******************************************************************************/ 12 | 13 | export type Affect = (r: R) => Promise>; 14 | 15 | /******************************************************************************* 16 | * Kind Registration 17 | ******************************************************************************/ 18 | 19 | export const URI = "Affect"; 20 | 21 | export type URI = typeof URI; 22 | 23 | export type URIS = HKT.URIS; 24 | 25 | declare module "./hkt.ts" { 26 | // deno-lint-ignore no-explicit-any 27 | export interface Kinds<_ extends any[]> { 28 | [URI]: Affect<_[2], _[1], _[0]>; 29 | } 30 | } 31 | 32 | /******************************************************************************* 33 | * Utilites 34 | ******************************************************************************/ 35 | 36 | export const make = (a: A): Promise => Promise.resolve(a); 37 | 38 | export const then = (fab: (a: A) => B) => 39 | (p: Promise): Promise => p.then(fab); 40 | 41 | /******************************************************************************* 42 | * Constructors 43 | ******************************************************************************/ 44 | 45 | export const ask = (): Affect => flow(E.right, make); 46 | 47 | export const askLeft = (): Affect => flow(E.left, make); 48 | 49 | export const asks = ( 50 | fra: (r: R) => Promise, 51 | ): Affect => flow(fra, then(E.right)); 52 | 53 | export const asksLeft = ( 54 | fre: (r: R) => Promise, 55 | ): Affect => flow(fre, then(E.left)); 56 | 57 | export const right = ( 58 | right: A, 59 | ): Affect => () => make(E.right(right)); 60 | 61 | export const left = ( 62 | left: E, 63 | ): Affect => () => make(E.left(left)); 64 | 65 | /******************************************************************************* 66 | * Modules 67 | ******************************************************************************/ 68 | 69 | export const Functor: TC.Functor = { 70 | map: (fai) => (ta) => flow(ta, then(E.map(fai))), 71 | }; 72 | 73 | export const Bifunctor: TC.Bifunctor = { 74 | bimap: (fbj, fai) => (ta) => flow(ta, then(E.bimap(fbj, fai))), 75 | mapLeft: (fbj) => (ta) => flow(ta, then(E.mapLeft(fbj))), 76 | }; 77 | 78 | export const Apply: TC.Apply = { 79 | ap: (tfab) => 80 | (ta) => 81 | async (r) => { 82 | const efab = await tfab(r); 83 | const ea = await ta(r); 84 | return pipe( 85 | ea, 86 | E.ap(efab), 87 | ); 88 | }, 89 | map: Functor.map, 90 | }; 91 | 92 | export const Applicative: TC.Applicative = { 93 | of: right, 94 | ap: Apply.ap, 95 | map: Functor.map, 96 | }; 97 | 98 | export const Chain: TC.Chain = { 99 | ap: Apply.ap, 100 | map: Functor.map, 101 | chain: (fati) => 102 | (ta) => 103 | async (r) => { 104 | const ea = await ta(r); 105 | return E.isLeft(ea) ? ea : fati(ea.right)(r); 106 | }, 107 | }; 108 | 109 | export const Monad: TC.Monad = { 110 | of: Applicative.of, 111 | ap: Apply.ap, 112 | map: Functor.map, 113 | join: Chain.chain(identity), 114 | chain: Chain.chain, 115 | }; 116 | 117 | export const MonadThrow: TC.MonadThrow = { 118 | ...Monad, 119 | throwError: left, 120 | }; 121 | 122 | /******************************************************************************* 123 | * Pipeables 124 | ******************************************************************************/ 125 | 126 | export const { of, ap, map, join, chain, throwError } = MonadThrow; 127 | 128 | export const { bimap, mapLeft } = Bifunctor; 129 | 130 | export const sequenceTuple = createSequenceTuple(Apply); 131 | 132 | export const sequenceStruct = createSequenceStruct(Apply); 133 | 134 | export const compose = ( 135 | aeb: Affect, 136 | ) => 137 | (rea: Affect): Affect => 138 | async (r) => { 139 | const ea = await rea(r); 140 | return E.isLeft(ea) ? ea : await aeb(ea.right); 141 | }; 142 | 143 | export const recover = (fea: (e: E) => A) => 144 | (ta: Affect): Affect => 145 | flow(ta, then(E.fold(flow(fea, E.right), E.right))); 146 | 147 | /******************************************************************************* 148 | * Do 149 | ******************************************************************************/ 150 | 151 | export const { Do, bind, bindTo } = createDo(Monad); 152 | -------------------------------------------------------------------------------- /const.ts: -------------------------------------------------------------------------------- 1 | import type * as HKT from "./hkt.ts"; 2 | import type * as TC from "./type_classes.ts"; 3 | 4 | import { identity } from "./fns.ts"; 5 | 6 | /******************************************************************************* 7 | * Types 8 | ******************************************************************************/ 9 | 10 | export type Const = E; 11 | 12 | /******************************************************************************* 13 | * Kind Registration 14 | ******************************************************************************/ 15 | 16 | export const URI = "Const"; 17 | 18 | export type URI = typeof URI; 19 | 20 | declare module "./hkt.ts" { 21 | // deno-lint-ignore no-explicit-any 22 | export interface Kinds<_ extends any[]> { 23 | [URI]: Const<_[1], _[0]>; 24 | } 25 | } 26 | 27 | /******************************************************************************* 28 | * Constructors 29 | ******************************************************************************/ 30 | 31 | export const make = (e: E): Const => e; 32 | 33 | /******************************************************************************* 34 | * Module Getters 35 | ******************************************************************************/ 36 | 37 | export const getShow = (S: TC.Show): TC.Show> => ({ 38 | show: (c) => `Const(${S.show(c)})`, 39 | }); 40 | 41 | export const getSetoid: ( 42 | E: TC.Setoid, 43 | ) => TC.Setoid> = identity; 44 | 45 | export const getOrd: (O: TC.Ord) => TC.Ord> = identity; 46 | 47 | export const getSemigroup: ( 48 | S: TC.Semigroup, 49 | ) => TC.Semigroup> = identity; 50 | 51 | export const getMonoid: ( 52 | M: TC.Monoid, 53 | ) => TC.Monoid> = identity; 54 | 55 | export const getApply = ( 56 | S: TC.Semigroup, 57 | ): TC.Apply => ({ 58 | map: (_) => (ta) => ta, 59 | // deno-lint-ignore no-explicit-any 60 | ap: (tfai) => (ta): Const => make(S.concat(tfai)(ta)), 61 | }); 62 | 63 | export const getApplicative = ( 64 | M: TC.Monoid, 65 | ): TC.Applicative => ({ 66 | // deno-lint-ignore no-explicit-any 67 | of: (): Const => make(M.empty()), 68 | ...getApply(M), 69 | }); 70 | 71 | /******************************************************************************* 72 | * Modules 73 | ******************************************************************************/ 74 | 75 | export const Functor: TC.Functor = { 76 | map: (_) => (ta) => ta, 77 | }; 78 | 79 | export const Contravariant: TC.Contravariant = { 80 | contramap: (_) => (tb) => tb, 81 | }; 82 | 83 | export const Bifunctor: TC.Bifunctor = { 84 | bimap: (fab, _) => (tac) => make(fab(tac)), 85 | mapLeft: (fef) => Bifunctor.bimap(fef, identity), 86 | }; 87 | -------------------------------------------------------------------------------- /derivations.ts: -------------------------------------------------------------------------------- 1 | import type * as TC from "./type_classes.ts"; 2 | import type { Kind, URIS } from "./hkt.ts"; 3 | 4 | import { identity, pipe } from "./fns.ts"; 5 | 6 | /******************************************************************************* 7 | * Module Derivations 8 | ******************************************************************************/ 9 | 10 | /** 11 | * Derive Monad module from of and chain 12 | */ 13 | // export const createMonad = ( 14 | // { of, chain }: Pick, "of" | "chain">, 15 | // ): TC.Monad => { 16 | // const Monad: TC.Monad = { 17 | // of, 18 | // ap: (tfai) => (ta) => pipe(tfai, chain((fab) => pipe(ta, Monad.map(fab)))), 19 | // map: (fai) => (ta) => pipe(ta, chain((a) => of(fai(a)))), 20 | // chain, 21 | // join: chain(identity), 22 | // }; 23 | // return Monad; 24 | // }; 25 | 26 | /******************************************************************************* 27 | * Do notation Derivation 28 | ******************************************************************************/ 29 | 30 | export const createDo = ( 31 | M: TC.Monad, 32 | ) => ({ 33 | // deno-lint-ignore ban-types 34 | Do: () => M.of<{}, B, C, D>({}), 35 | bindTo: ( 36 | name: N, 37 | ): (( 38 | ta: Kind, 39 | ) => Kind) => 40 | // deno-lint-ignore no-explicit-any 41 | M.map((a: any): any => ({ [name]: a })), 42 | bind: ( 43 | name: Exclude, 44 | fati: (a: A) => Kind, 45 | ): ( 46 | ( 47 | ma: Kind, 48 | ) => Kind< 49 | URI, 50 | [{ readonly [K in keyof A | N]: K extends keyof A ? A[K] : I }, B, C, D] 51 | > 52 | ) => 53 | // deno-lint-ignore no-explicit-any 54 | M.chain((a: any): any => 55 | // deno-lint-ignore no-explicit-any 56 | pipe(a, fati, M.map((b: any): any => ({ ...a, [name]: b }))) 57 | ), 58 | }); 59 | 60 | /******************************************************************************* 61 | * Derive getSemigroup from Apply 62 | ******************************************************************************/ 63 | 64 | export const createApplySemigroup = (A: TC.Apply) => 65 | ( 66 | S: TC.Semigroup, 67 | ): TC.Semigroup> => ({ 68 | concat: (a) => A.ap(pipe(a, A.map(S.concat))), 69 | }); 70 | -------------------------------------------------------------------------------- /examples/array/ap.ts: -------------------------------------------------------------------------------- 1 | import * as A from "../../array.ts"; 2 | import { pipe } from "../../fns.ts"; 3 | 4 | /** 5 | * Here we s 6 | */ 7 | 8 | const addOne = (n: number) => ({ type: "addOne", n, out: n + 1 }); 9 | const square = (n: number) => ({ type: "square", n, out: n * n }); 10 | const arrayOfFunctions = [addOne, square]; 11 | const arrayOfNumbers = [1, 2, 3]; 12 | 13 | const result1 = pipe( 14 | arrayOfNumbers, 15 | A.ap(arrayOfFunctions), // Applies every function to every number 16 | ); 17 | 18 | console.log(JSON.stringify(result1, null, 2)); 19 | /* 20 | [ 21 | { 22 | "type": "addOne", 23 | "n": 1, 24 | "out": 2 25 | }, 26 | { 27 | "type": "addOne", 28 | "n": 2, 29 | "out": 3 30 | }, 31 | { 32 | "type": "addOne", 33 | "n": 3, 34 | "out": 4 35 | }, 36 | { 37 | "type": "square", 38 | "n": 1, 39 | "out": 1 40 | }, 41 | { 42 | "type": "square", 43 | "n": 2, 44 | "out": 4 45 | }, 46 | { 47 | "type": "square", 48 | "n": 3, 49 | "out": 9 50 | } 51 | ] 52 | */ 53 | -------------------------------------------------------------------------------- /examples/array/traverse.ts: -------------------------------------------------------------------------------- 1 | import * as A from "../../array.ts"; 2 | import * as O from "../../option.ts"; 3 | 4 | const traverse = A.traverse(O.Applicative); 5 | 6 | const criteria = O.fromPredicate((n: number) => n > 0); 7 | 8 | // Will look through an array, if any of the criteria don't match it will return none, 9 | // otherwise it will wrap the array in some 10 | const positiveNumberTraverse = traverse(criteria); 11 | 12 | const arrayOfNumbers1 = [1, 2, 3, 4, 5]; 13 | 14 | const arrayOfNumbers2 = [1, 2, 3, 4, -5]; 15 | 16 | const result1 = positiveNumberTraverse(arrayOfNumbers1); 17 | const result2 = positiveNumberTraverse(arrayOfNumbers2); 18 | 19 | console.log(result1); // { tag: "Some", value: [ 1, 2, 3, 4, 5 ] } 20 | console.log(result2); // { tag: "None" } 21 | -------------------------------------------------------------------------------- /examples/do.ts: -------------------------------------------------------------------------------- 1 | import * as O from "../option.ts"; 2 | import { pipe } from "../fns.ts"; 3 | 4 | const t1 = pipe( 5 | O.some("Hello"), 6 | O.bindTo("one"), 7 | O.bind("two", ({ one }) => O.some(`${one} World`)), 8 | ); 9 | 10 | console.log(t1); 11 | -------------------------------------------------------------------------------- /examples/either/tryCatch.ts: -------------------------------------------------------------------------------- 1 | import * as E from "../../either.ts"; 2 | 3 | const mightThrow = (n: number) => { 4 | if (n > 0) { 5 | return n.toString(); 6 | } 7 | throw new Error("Number out of range!"); 8 | }; 9 | 10 | const noThrow = E.tryCatchWrap(mightThrow, String); 11 | 12 | const result1 = noThrow(1); 13 | const result2 = noThrow(-1); 14 | 15 | console.log(result1); // { tag: "Right", right: "1" } 16 | console.log(result2); // { tag: "Left", left: "Error: Number out of range!" } 17 | -------------------------------------------------------------------------------- /examples/optics.ts: -------------------------------------------------------------------------------- 1 | import * as L from "../optics/lens.ts"; 2 | import { pipe } from "../fns.ts"; 3 | 4 | type T1 = { 5 | one: number; 6 | two: { 7 | three: string; 8 | four: string; 9 | five: string; 10 | }; 11 | }; 12 | 13 | export const l1 = pipe( 14 | L.id(), 15 | L.prop("one"), 16 | ); 17 | 18 | export const l2 = pipe( 19 | L.id(), 20 | L.prop("two"), 21 | L.props("three", "four"), 22 | ); 23 | 24 | const v1: T1 = { 25 | one: 1, 26 | two: { 27 | three: "three", 28 | four: "four", 29 | five: "five", 30 | }, 31 | }; 32 | 33 | const v2: T1 = { 34 | one: 1, 35 | two: { 36 | three: "3", 37 | four: "4", 38 | five: "5", 39 | }, 40 | }; 41 | 42 | const vs = [v1, v2]; 43 | 44 | vs.forEach((v) => { 45 | console.log("Get", { l1: l1.get(v), l2: l2.get(v) }); 46 | console.log("Set", { 47 | l1: l1.set(10)(v), 48 | l2: l2.set({ three: "junk", four: "cludge" })(v), 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /examples/schemable.ts: -------------------------------------------------------------------------------- 1 | import * as S from "../schemable/schemable.ts"; 2 | import * as D from "../schemable/decoder.ts"; 3 | import * as E from "../either.ts"; 4 | import { flow } from "../fns.ts"; 5 | 6 | export const Thing = S.make((s) => s.array(s.literal("shit"))); 7 | export type Thing = S.TypeOf; 8 | 9 | export const Demo = S.make((s) => 10 | s.struct({ 11 | one: s.string(), 12 | two: s.partial({ 13 | three: s.string(), 14 | four: s.literal(1, 2), 15 | }), 16 | things: Thing(s), 17 | }) 18 | ); 19 | export type Demo = S.TypeOf; 20 | 21 | export const decode = Demo(D.Schemable); 22 | 23 | export const print = flow( 24 | decode, 25 | E.mapLeft(D.draw), 26 | E.fold(console.error, console.log), 27 | ); 28 | 29 | [null, undefined, {}, [], { one: "one", two: {}, things: [] }, { 30 | one: "one", 31 | things: ["shit", "poop"], 32 | }].forEach(print); 33 | -------------------------------------------------------------------------------- /examples/semigroup.ts: -------------------------------------------------------------------------------- 1 | import * as S from "../semigroup.ts"; 2 | 3 | export type T1 = { 4 | one: string; 5 | two: number; 6 | }; 7 | 8 | const mySemi = S.getStructSemigroup({ 9 | one: S.getFirstSemigroup(), 10 | two: S.semigroupProduct, 11 | }); 12 | 13 | const t1: T1 = { one: "Hello", two: 10 }; 14 | const t2: T1 = { one: "World", two: 12 }; 15 | 16 | const r1 = mySemi.concat(t1)(t2); 17 | 18 | console.log(r1); 19 | -------------------------------------------------------------------------------- /hkt.ts: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Kinds Type 3 | * 4 | * A registry for Kind URIS with their substitution strategies. _ represents 5 | * a tuple of types used to fill a specific Kind. 6 | * 7 | * Note that the idiomatic replacement type for kinds uses _[0] as the inner- 8 | * most hole for Functor, Apply, Chain, etc. Thus Either should use the 9 | * hole order of Either<_[1], _[0]>, Reader should use Reader<_[1], _[0]>, 10 | * etc. 11 | ******************************************************************************/ 12 | 13 | // deno-lint-ignore no-explicit-any 14 | export interface Kinds<_ extends any[]> { 15 | "_": _; 16 | } 17 | 18 | /******************************************************************************* 19 | * URIS Type 20 | * 21 | * A union of all Kind URIS, used primarily to ensure that a Kind is registered 22 | * before it is used to construct an instance. 23 | ******************************************************************************/ 24 | 25 | // deno-lint-ignore no-explicit-any 26 | export type URIS = keyof Kinds; 27 | 28 | /******************************************************************************* 29 | * Kind Type 30 | * 31 | * Lookup the kind at URI and substitute with the values in _. 32 | ******************************************************************************/ 33 | 34 | // deno-lint-ignore no-explicit-any 35 | export type Kind = Kinds<_>[URI]; 36 | 37 | /******************************************************************************* 38 | * Extraction Types 39 | * 40 | * Extract the type at a specific index of a Kind type 41 | ******************************************************************************/ 42 | // deno-lint-ignore no-explicit-any 43 | export type TypeOf = T extends Kind 44 | ? _[N] 45 | : never; 46 | 47 | // deno-lint-ignore no-explicit-any 48 | export type URIof = T extends Kind ? URI : never; 49 | -------------------------------------------------------------------------------- /identity.ts: -------------------------------------------------------------------------------- 1 | import type * as HKT from "./hkt.ts"; 2 | import type * as TC from "./type_classes.ts"; 3 | 4 | import { call, identity } from "./fns.ts"; 5 | 6 | /******************************************************************************* 7 | * Types 8 | ******************************************************************************/ 9 | 10 | /******************************************************************************* 11 | * Identity 12 | * 13 | * The identity type returns exactly the type that is passed into it. 14 | ******************************************************************************/ 15 | export type Identity = A; 16 | 17 | /******************************************************************************* 18 | * Kind Registration 19 | ******************************************************************************/ 20 | 21 | /******************************************************************************* 22 | * The Kinds URI for Identity 23 | ******************************************************************************/ 24 | export const URI = "Identity"; 25 | 26 | /******************************************************************************* 27 | * The Kinds URI Type for Identity 28 | ******************************************************************************/ 29 | export type URI = typeof URI; 30 | 31 | declare module "./hkt.ts" { 32 | // deno-lint-ignore no-explicit-any 33 | export interface Kinds<_ extends any[]> { 34 | [URI]: Identity<_[0]>; 35 | } 36 | } 37 | 38 | /******************************************************************************* 39 | * Modules 40 | ******************************************************************************/ 41 | 42 | /******************************************************************************* 43 | * The standard Functor instance for Identity 44 | ******************************************************************************/ 45 | export const Functor: TC.Functor = { 46 | map: identity, 47 | }; 48 | 49 | /******************************************************************************* 50 | * The standard Apply instance for Identity 51 | ******************************************************************************/ 52 | export const Apply: TC.Apply = { 53 | ap: call, 54 | map: Functor.map, 55 | }; 56 | 57 | export const Applicative: TC.Applicative = { 58 | of: identity, 59 | ap: Apply.ap, 60 | map: Functor.map, 61 | }; 62 | 63 | export const Chain: TC.Chain = { 64 | ap: Apply.ap, 65 | map: Functor.map, 66 | chain: identity, 67 | }; 68 | 69 | export const Monad: TC.Monad = { 70 | of: Applicative.of, 71 | ap: Apply.ap, 72 | map: Functor.map, 73 | join: identity, 74 | chain: Chain.chain, 75 | }; 76 | 77 | /******************************************************************************* 78 | * Pipeables 79 | ******************************************************************************/ 80 | 81 | export const { of, ap, map, join, chain } = Monad; 82 | -------------------------------------------------------------------------------- /io.ts: -------------------------------------------------------------------------------- 1 | import type * as HKT from "./hkt.ts"; 2 | import type * as TC from "./type_classes.ts"; 3 | 4 | import { createSequenceStruct, createSequenceTuple } from "./sequence.ts"; 5 | import { apply, constant, flow, pipe } from "./fns.ts"; 6 | import { createApplySemigroup, createDo } from "./derivations.ts"; 7 | 8 | /******************************************************************************* 9 | * Types 10 | ******************************************************************************/ 11 | 12 | export type IO = () => A; 13 | 14 | /******************************************************************************* 15 | * Kind Registration 16 | ******************************************************************************/ 17 | 18 | export const URI = "IO"; 19 | 20 | export type URI = typeof URI; 21 | 22 | declare module "./hkt.ts" { 23 | // deno-lint-ignore no-explicit-any 24 | export interface Kinds<_ extends any[]> { 25 | [URI]: IO<_[0]>; 26 | } 27 | } 28 | 29 | /******************************************************************************* 30 | * Modules 31 | ******************************************************************************/ 32 | 33 | export const Functor: TC.Functor = { 34 | map: (fab) => (ta) => flow(ta, fab), 35 | }; 36 | 37 | export const Apply: TC.Apply = { 38 | ap: (tfab) => (ta) => () => tfab()(ta()), 39 | map: Functor.map, 40 | }; 41 | 42 | export const Applicative: TC.Applicative = { 43 | of: constant, 44 | ap: Apply.ap, 45 | map: Functor.map, 46 | }; 47 | 48 | export const Chain: TC.Chain = { 49 | ap: Apply.ap, 50 | map: Functor.map, 51 | chain: (fatb) => (ta) => flow(ta, fatb, apply()), 52 | }; 53 | 54 | export const Monad: TC.Monad = { 55 | of: Applicative.of, 56 | ap: Apply.ap, 57 | map: Functor.map, 58 | join: apply(), 59 | chain: Chain.chain, 60 | }; 61 | 62 | export const Extends: TC.Extend = { 63 | map: Monad.map, 64 | extend: (ftab) => (ta) => () => ftab(ta), 65 | }; 66 | 67 | export const Foldable: TC.Foldable = { 68 | reduce: (faba, a) => (tb) => faba(a, tb()), 69 | }; 70 | 71 | export const Traversable: TC.Traversable = { 72 | map: Monad.map, 73 | reduce: Foldable.reduce, 74 | traverse: (A) => (faub) => (ta) => pipe(faub(ta()), A.map(of)), 75 | }; 76 | 77 | /******************************************************************************* 78 | * Module Getters 79 | ******************************************************************************/ 80 | 81 | export const getSemigroup = createApplySemigroup(Apply); 82 | 83 | export const getMonoid = (M: TC.Monoid): TC.Monoid> => ({ 84 | ...getSemigroup(M), 85 | empty: constant(M.empty), 86 | }); 87 | 88 | /******************************************************************************* 89 | * Pipeables 90 | ******************************************************************************/ 91 | 92 | export const { of, ap, map, join, chain } = Monad; 93 | 94 | export const { reduce, traverse } = Traversable; 95 | 96 | export const { extend } = Extends; 97 | 98 | /******************************************************************************* 99 | * Sequenec 100 | ******************************************************************************/ 101 | 102 | export const sequenceTuple = createSequenceTuple(Apply); 103 | 104 | export const sequenceStruct = createSequenceStruct(Apply); 105 | 106 | /******************************************************************************* 107 | * Do Notation 108 | ******************************************************************************/ 109 | 110 | export const { Do, bind, bindTo } = createDo(Monad); 111 | -------------------------------------------------------------------------------- /io_either.ts: -------------------------------------------------------------------------------- 1 | import type * as HKT from "./hkt.ts"; 2 | import type * as TC from "./type_classes.ts"; 3 | import type { Lazy } from "./types.ts"; 4 | 5 | import * as E from "./either.ts"; 6 | import * as I from "./io.ts"; 7 | import * as S from "./sequence.ts"; 8 | import { createDo } from "./derivations.ts"; 9 | import { constant, flow, identity, pipe } from "./fns.ts"; 10 | 11 | /******************************************************************************* 12 | * Types 13 | ******************************************************************************/ 14 | 15 | export type IOEither = I.IO>; 16 | 17 | /******************************************************************************* 18 | * Kind Registration 19 | ******************************************************************************/ 20 | 21 | export const URI = "IOEither"; 22 | 23 | export type URI = typeof URI; 24 | 25 | declare module "./hkt.ts" { 26 | // deno-lint-ignore no-explicit-any 27 | export interface Kinds<_ extends any[]> { 28 | [URI]: IOEither<_[1], _[0]>; 29 | } 30 | } 31 | 32 | /******************************************************************************* 33 | * Constructors 34 | ******************************************************************************/ 35 | 36 | export const left = (left: E): IOEither => 37 | I.of(E.left(left)); 38 | 39 | export const right = (right: A): IOEither => 40 | I.of(E.right(right)); 41 | 42 | export const tryCatch = ( 43 | f: Lazy, 44 | onError: (e: unknown) => E, 45 | ): IOEither => { 46 | try { 47 | return right(f()); 48 | } catch (e) { 49 | return left(onError(e)); 50 | } 51 | }; 52 | 53 | export const fromEither = (ta: E.Either): IOEither => 54 | constant(ta); 55 | 56 | /******************************************************************************* 57 | * Modules 58 | ******************************************************************************/ 59 | 60 | export const Functor: TC.Functor = { 61 | map: (fab) => (ta) => flow(ta, E.map(fab)), 62 | }; 63 | 64 | export const Apply: TC.Apply = { 65 | ap: (tfab) => 66 | (ta) => 67 | () => 68 | pipe( 69 | E.sequenceTuple(tfab(), ta()), 70 | E.map((s) => s[0](s[1])), 71 | ), 72 | map: Functor.map, 73 | }; 74 | 75 | export const Applicative: TC.Applicative = { 76 | of: right, 77 | ap: Apply.ap, 78 | map: Functor.map, 79 | }; 80 | 81 | export const Chain: TC.Chain = { 82 | ap: Apply.ap, 83 | map: Functor.map, 84 | chain: (fatb) => (ta) => flow(ta, E.fold(E.left, (a) => fatb(a)())), 85 | }; 86 | 87 | export const Monad: TC.Monad = { 88 | of: Applicative.of, 89 | ap: Apply.ap, 90 | map: Functor.map, 91 | join: Chain.chain(identity), 92 | chain: Chain.chain, 93 | }; 94 | 95 | export const Bifunctor: TC.Bifunctor = { 96 | bimap: flow(E.bimap, I.map), 97 | mapLeft: (fef) => (ta) => pipe(ta, I.map(E.mapLeft(fef))), 98 | }; 99 | 100 | export const MonadThrow: TC.MonadThrow = { 101 | of: Applicative.of, 102 | ap: Apply.ap, 103 | map: Functor.map, 104 | join: Monad.join, 105 | chain: Chain.chain, 106 | throwError: left, 107 | }; 108 | 109 | export const Alt: TC.Alt = ({ 110 | map: Monad.map, 111 | alt: (tb) => (ta) => flow(ta, E.fold(tb, E.right)), 112 | }); 113 | 114 | export const Extends: TC.Extend = { 115 | map: Functor.map, 116 | extend: (tfab) => flow(tfab, right), 117 | }; 118 | 119 | export const Foldable: TC.Foldable = { 120 | reduce: (faba, a) => 121 | (tb) => 122 | pipe( 123 | tb(), 124 | E.fold(() => a, (b) => faba(a, b)), 125 | ), 126 | }; 127 | 128 | /******************************************************************************* 129 | * Pipeables 130 | ******************************************************************************/ 131 | 132 | export const { of, ap, map, join, chain, throwError } = MonadThrow; 133 | 134 | export const { bimap, mapLeft } = Bifunctor; 135 | 136 | export const { reduce } = Foldable; 137 | 138 | export const { extend } = Extends; 139 | 140 | export const { alt } = Alt; 141 | 142 | export const chainLeft = (onLeft: (e: E) => IOEither) => 143 | (ma: IOEither): IOEither => 144 | pipe( 145 | ma, 146 | I.chain(E.fold(onLeft, right)), 147 | ); 148 | 149 | export const widen: () => ( 150 | ta: IOEither, 151 | ) => IOEither = constant(identity); 152 | 153 | /******************************************************************************* 154 | * Sequence 155 | ******************************************************************************/ 156 | 157 | export const sequenceTuple = S.createSequenceTuple(Apply); 158 | 159 | export const sequenceStruct = S.createSequenceStruct(Apply); 160 | 161 | /******************************************************************************* 162 | * Do Notation 163 | ******************************************************************************/ 164 | 165 | export const { Do, bind, bindTo } = createDo(Monad); 166 | -------------------------------------------------------------------------------- /monoid.ts: -------------------------------------------------------------------------------- 1 | import type { Monoid, Semigroup } from "./type_classes.ts"; 2 | 3 | import { constant, pipe } from "./fns.ts"; 4 | import * as S from "./semigroup.ts"; 5 | 6 | /******************************************************************************* 7 | * Module Instances 8 | ******************************************************************************/ 9 | 10 | export const monoidAll: Monoid = { 11 | concat: S.semigroupAll.concat, 12 | empty: constant(true), 13 | }; 14 | 15 | export const monoidAny: Monoid = { 16 | concat: S.semigroupAny.concat, 17 | empty: constant(false), 18 | }; 19 | 20 | export const monoidSum: Monoid = { 21 | concat: S.semigroupSum.concat, 22 | empty: constant(0), 23 | }; 24 | 25 | export const monoidProduct: Monoid = { 26 | concat: S.semigroupProduct.concat, 27 | empty: constant(1), 28 | }; 29 | 30 | export const monoidString: Monoid = { 31 | concat: S.semigroupString.concat, 32 | empty: constant(""), 33 | }; 34 | 35 | export const monoidVoid: Monoid = { 36 | concat: S.semigroupVoid.concat, 37 | empty: constant(undefined), 38 | }; 39 | 40 | /******************************************************************************* 41 | * Module Getters 42 | ******************************************************************************/ 43 | 44 | // deno-lint-ignore no-explicit-any 45 | export const getTupleMonoid = >>( 46 | ...monoids: T 47 | ): Monoid<{ [K in keyof T]: T[K] extends Semigroup ? A : never }> => { 48 | const concat = S.getTupleSemigroup(...monoids).concat; 49 | return (({ 50 | concat, 51 | empty: () => monoids.map((m) => m.empty()), 52 | }) as unknown) as Monoid< 53 | { [K in keyof T]: T[K] extends Semigroup ? A : never } 54 | >; 55 | }; 56 | 57 | export const getDualMonoid = (M: Monoid): Monoid => ({ 58 | concat: S.getDualSemigroup(M).concat, 59 | empty: M.empty, 60 | }); 61 | 62 | // deno-lint-ignore no-explicit-any 63 | export const getStructMonoid = >( 64 | monoids: { [K in keyof O]: Monoid }, 65 | ): Monoid<{ readonly [K in keyof O]: O[K] }> => { 66 | const empty: Record = {}; 67 | for (const key of Object.keys(monoids)) { 68 | empty[key] = monoids[key].empty(); 69 | } 70 | return { 71 | concat: S.getStructSemigroup(monoids).concat, 72 | empty: () => (empty as unknown) as O, 73 | }; 74 | }; 75 | 76 | /******************************************************************************* 77 | * Pipeables 78 | ******************************************************************************/ 79 | 80 | export const fold = (M: Monoid) => { 81 | const inner_fold = S.fold(M); 82 | return (as: ReadonlyArray): A => pipe(as, inner_fold(M.empty())); 83 | }; 84 | -------------------------------------------------------------------------------- /nilable.ts: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Nilable 3 | * Note: The Nilable Functor is not a true Functor as it does not satisfy the functor laws. 4 | * However, it is still fairly useful. 5 | * 6 | * Nilable is a type like Maybe/Option that uses undefined/null in lieu of tagged unions. 7 | ******************************************************************************/ 8 | 9 | import type * as HKT from "./hkt.ts"; 10 | import type * as TC from "./type_classes.ts"; 11 | import type { Lazy, Predicate } from "./types.ts"; 12 | 13 | import { identity } from "./fns.ts"; 14 | import { createDo } from "./derivations.ts"; 15 | import { createSequenceStruct, createSequenceTuple } from "./sequence.ts"; 16 | 17 | /******************************************************************************* 18 | * Types 19 | ******************************************************************************/ 20 | 21 | export type Nil = undefined | null; 22 | 23 | export type Nilable = Nil | A; 24 | 25 | /******************************************************************************* 26 | * Kind Registration 27 | ******************************************************************************/ 28 | 29 | export const URI = "Nilable"; 30 | 31 | export type URI = typeof URI; 32 | 33 | declare module "./hkt.ts" { 34 | // deno-lint-ignore no-explicit-any 35 | export interface Kinds<_ extends any[]> { 36 | [URI]: Nilable<_[0]>; 37 | } 38 | } 39 | 40 | /******************************************************************************* 41 | * Constructors 42 | ******************************************************************************/ 43 | 44 | export const nil: Nil = undefined; 45 | 46 | export const constNil = (): Nilable => nil; 47 | 48 | export const make = (a: A): Nilable => 49 | isNotNil(a) ? a : nil; 50 | 51 | export const fromPredicate = (predicate: Predicate) => 52 | (ta: Nilable): Nilable => isNotNil(ta) && predicate(ta) ? ta : nil; 53 | 54 | export const tryCatch = (f: Lazy): Nilable => { 55 | try { 56 | return f(); 57 | } catch (e) { 58 | return nil; 59 | } 60 | }; 61 | 62 | /******************************************************************************* 63 | * Destructors 64 | ******************************************************************************/ 65 | 66 | export const fold = (onNil: () => B, onValue: (a: A) => B) => 67 | (ta: Nilable): B => (isNil(ta) ? onNil() : onValue(ta)); 68 | 69 | export const getOrElse = (onNil: () => B) => 70 | (ta: Nilable): B => isNil(ta) ? onNil() : ta; 71 | 72 | export const toNull = (ma: Nilable): A | null => isNil(ma) ? null : ma; 73 | 74 | export const toUndefined = (ma: Nilable): A | undefined => 75 | isNil(ma) ? undefined : ma; 76 | 77 | /******************************************************************************* 78 | * Guards 79 | ******************************************************************************/ 80 | 81 | export const isNil = (m: Nilable): m is Nil => 82 | m === undefined || m === null; 83 | 84 | export const isNotNil = (m: Nilable): m is NonNullable => !isNil(m); 85 | 86 | /******************************************************************************* 87 | * Modules (Note that these modules do not follow the Type Class laws) 88 | ******************************************************************************/ 89 | 90 | export const Functor: TC.Functor = { 91 | map: (fab) => (ta) => isNil(ta) ? nil : fab(ta), 92 | }; 93 | 94 | export const Apply: TC.Apply = { 95 | ap: (tfab) => (ta) => isNil(ta) || isNil(tfab) ? nil : tfab(ta), 96 | map: Functor.map, 97 | }; 98 | 99 | export const Applicative: TC.Applicative = { 100 | of: identity, 101 | ap: Apply.ap, 102 | map: Functor.map, 103 | }; 104 | 105 | export const Chain: TC.Chain = { 106 | ap: Apply.ap, 107 | map: Functor.map, 108 | chain: Functor.map, 109 | }; 110 | 111 | export const Monad: TC.Monad = { 112 | of: Applicative.of, 113 | ap: Apply.ap, 114 | map: Functor.map, 115 | join: Chain.chain(identity), 116 | chain: Chain.chain, 117 | }; 118 | 119 | /******************************************************************************* 120 | * Module Getters 121 | ******************************************************************************/ 122 | 123 | export const getShow = ({ show }: TC.Show): TC.Show> => ({ 124 | show: (ma) => (isNil(ma) ? "nil" : show(ma)), 125 | }); 126 | 127 | /******************************************************************************* 128 | * Pipeables 129 | ******************************************************************************/ 130 | 131 | export const { of, ap, map, join, chain } = Monad; 132 | 133 | export const sequenceStruct = createSequenceStruct(Apply); 134 | 135 | export const sequenceTuple = createSequenceTuple(Apply); 136 | 137 | /******************************************************************************* 138 | * Do Notation 139 | ******************************************************************************/ 140 | 141 | export const { Do, bind, bindTo } = createDo(Monad); 142 | -------------------------------------------------------------------------------- /optics/at.ts: -------------------------------------------------------------------------------- 1 | import type { Lens } from "./lens.ts"; 2 | 3 | import * as O from "../option.ts"; 4 | import * as R from "../record.ts"; 5 | 6 | /******************************************************************************* 7 | * Models 8 | ******************************************************************************/ 9 | 10 | export type At = { 11 | readonly at: (i: I) => Lens; 12 | }; 13 | 14 | /******************************************************************************* 15 | * Instance Getters 16 | ******************************************************************************/ 17 | 18 | export const atRecord = (): At< 19 | Readonly>, 20 | string, 21 | O.Option 22 | > => ({ 23 | at: (key) => ({ 24 | get: (r) => O.fromNullable(r[key]), 25 | set: O.fold( 26 | () => R.deleteAt(key), 27 | (a) => R.insertAt(key, a), 28 | ), 29 | }), 30 | }); 31 | 32 | export const atMap = (): At< 33 | Map, 34 | string, 35 | O.Option 36 | > => ({ 37 | at: (key) => ({ 38 | get: (m) => O.fromNullable(m.get(key)), 39 | set: O.fold( 40 | () => 41 | (m) => { 42 | m.delete(key); 43 | return m; 44 | }, 45 | (a) => (m) => m.set(key, a), 46 | ), 47 | }), 48 | }); 49 | -------------------------------------------------------------------------------- /optics/from_traversable.ts: -------------------------------------------------------------------------------- 1 | import type * as TC from "../type_classes.ts"; 2 | import type { Traversal } from "./traversal.ts"; 3 | import type { Kind, URIS } from "../hkt.ts"; 4 | import { constant } from "../fns.ts"; 5 | 6 | /******************************************************************************* 7 | * Constructors 8 | ******************************************************************************/ 9 | 10 | export const fromTraversable = ( 11 | T: TC.Traversable, 12 | ): < 13 | A = never, 14 | B = never, 15 | C = never, 16 | D = never, 17 | > // deno-lint-ignore no-explicit-any 18 | () => Traversal, A> => constant(T) as any; 19 | -------------------------------------------------------------------------------- /optics/index.ts: -------------------------------------------------------------------------------- 1 | import type { Optional } from "./optional.ts"; 2 | 3 | import * as A from "../array.ts"; 4 | import * as O from "../option.ts"; 5 | import * as R from "../record.ts"; 6 | import { isNil, pipe } from "../fns.ts"; 7 | 8 | /******************************************************************************* 9 | * Models 10 | ******************************************************************************/ 11 | 12 | export type Index = { 13 | readonly index: (i: I) => Optional; 14 | }; 15 | 16 | /******************************************************************************* 17 | * Instance Getters 18 | ******************************************************************************/ 19 | 20 | export const indexArray = (): Index< 21 | ReadonlyArray, 22 | number, 23 | A 24 | > => ({ 25 | index: (i) => ({ 26 | getOption: A.lookup(i), 27 | set: (a) => 28 | (as) => 29 | pipe( 30 | A.updateAt(i, a)(as), 31 | O.getOrElse(() => as), 32 | ), 33 | }), 34 | }); 35 | 36 | export const indexRecord = (): Index< 37 | Readonly>, 38 | string, 39 | A 40 | > => ({ 41 | index: (k) => ({ 42 | getOption: (r) => O.fromNullable(r[k]), 43 | set: (a) => (r) => (r[k] === a || isNil(r[k]) ? r : R.insertAt(k, a)(r)), 44 | }), 45 | }); 46 | -------------------------------------------------------------------------------- /optics/iso.ts: -------------------------------------------------------------------------------- 1 | import type * as TC from "../type_classes.ts"; 2 | import type { Kind, URIS } from "../hkt.ts"; 3 | import type { Either } from "../either.ts"; 4 | import type { Option } from "../option.ts"; 5 | 6 | import type { Lens } from "./lens.ts"; 7 | import type { Optional } from "./optional.ts"; 8 | import type { Prism } from "./prism.ts"; 9 | import type { Traversal } from "./traversal.ts"; 10 | 11 | import { fromTraversable } from "./from_traversable.ts"; 12 | 13 | import * as O from "../option.ts"; 14 | import * as E from "../either.ts"; 15 | import { constant, flow, identity, pipe } from "../fns.ts"; 16 | 17 | /******************************************************************************* 18 | * Types 19 | ******************************************************************************/ 20 | 21 | export type Iso = { 22 | readonly get: (s: S) => A; 23 | readonly reverseGet: (s: A) => S; 24 | }; 25 | 26 | /******************************************************************************* 27 | * Constructors 28 | ******************************************************************************/ 29 | 30 | export const make = ( 31 | get: (a: A) => B, 32 | reverseGet: (b: B) => A, 33 | ): Iso => ({ 34 | get, 35 | reverseGet, 36 | }); 37 | 38 | /******************************************************************************* 39 | * Modules 40 | ******************************************************************************/ 41 | 42 | /******************************************************************************* 43 | * Converters 44 | ******************************************************************************/ 45 | 46 | export const asLens = (sa: Iso): Lens => ({ 47 | get: sa.get, 48 | set: flow(sa.reverseGet, constant), 49 | }); 50 | 51 | export const asPrism = (sa: Iso): Prism => ({ 52 | getOption: flow(sa.get, O.some), 53 | reverseGet: sa.reverseGet, 54 | }); 55 | 56 | export const asOptional = (sa: Iso): Optional => ({ 57 | getOption: flow(sa.get, O.some), 58 | set: flow(sa.reverseGet, constant), 59 | }); 60 | 61 | export const asTraversal = (sa: Iso): Traversal => ({ 62 | traverse: ({ map }) => 63 | (fata) => 64 | flow( 65 | sa.get, 66 | fata, 67 | map(sa.reverseGet), 68 | ), 69 | }); 70 | 71 | /******************************************************************************* 72 | * Pipeable Compose 73 | ******************************************************************************/ 74 | 75 | export const id = (): Iso => ({ 76 | get: identity, 77 | reverseGet: identity, 78 | }); 79 | 80 | export const compose = (jk: Iso) => 81 | (ij: Iso): Iso => ({ 82 | get: flow(ij.get, jk.get), 83 | reverseGet: flow(jk.reverseGet, ij.reverseGet), 84 | }); 85 | 86 | export const composeLens = (ab: Lens) => 87 | (sa: Iso): Lens => ({ 88 | get: flow(sa.get, ab.get), 89 | set: (b) => flow(sa.get, ab.set(b), sa.reverseGet), 90 | }); 91 | 92 | export const composePrism = (ab: Prism) => 93 | (sa: Iso): Prism => ({ 94 | getOption: flow(sa.get, ab.getOption), 95 | reverseGet: flow(ab.reverseGet, sa.reverseGet), 96 | }); 97 | 98 | export const composeOptional = (ab: Optional) => 99 | (sa: Iso): Optional => ({ 100 | getOption: flow(sa.get, ab.getOption), 101 | set: (b) => flow(sa.get, ab.set(b), sa.reverseGet), 102 | }); 103 | 104 | export const composeTraversal = (ab: Traversal) => 105 | (sa: Iso): Traversal => ({ 106 | traverse: (A) => 107 | (fata) => 108 | flow( 109 | sa.get, 110 | ab.traverse(A)(fata), 111 | A.map(sa.reverseGet), 112 | ), 113 | }); 114 | 115 | /******************************************************************************* 116 | * Pipeables 117 | ******************************************************************************/ 118 | 119 | export const modify = (f: (a: A) => A) => 120 | (sa: Iso) => (s: S): S => sa.reverseGet(f(sa.get(s))); 121 | 122 | export const map = (ab: (a: A) => B, ba: (b: B) => A) => 123 | (sa: Iso): Iso => ({ 124 | get: flow(sa.get, ab), 125 | reverseGet: flow(ba, sa.reverseGet), 126 | }); 127 | 128 | export const reverse = (sa: Iso): Iso => ({ 129 | get: sa.reverseGet, 130 | reverseGet: sa.get, 131 | }); 132 | 133 | export const traverse = (T: TC.Traversable) => { 134 | const _traversal = fromTraversable(T); 135 | return ( 136 | sa: Iso>, 137 | ): Traversal => 138 | pipe( 139 | sa, 140 | composeTraversal(_traversal()), 141 | ); 142 | }; 143 | 144 | /******************************************************************************* 145 | * Pipeable Over ADT 146 | ******************************************************************************/ 147 | 148 | export const some: (soa: Iso>) => Prism = composePrism( 149 | { 150 | getOption: identity, 151 | reverseGet: O.some, 152 | }, 153 | ); 154 | 155 | export const right: ( 156 | sea: Iso>, 157 | ) => Prism = composePrism({ 158 | getOption: E.getRight, 159 | reverseGet: E.right, 160 | }); 161 | 162 | export const left: ( 163 | sea: Iso>, 164 | ) => Prism = composePrism({ 165 | getOption: E.getLeft, 166 | reverseGet: E.left, 167 | }); 168 | -------------------------------------------------------------------------------- /optics/traversal.ts: -------------------------------------------------------------------------------- 1 | import type * as TC from "../type_classes.ts"; 2 | import type { Kind, URIS } from "../hkt.ts"; 3 | import type { Predicate, Refinement } from "../types.ts"; 4 | import type { Either } from "../either.ts"; 5 | import type { Option } from "../option.ts"; 6 | 7 | import * as I from "../identity.ts"; 8 | import * as O from "../option.ts"; 9 | import * as E from "../either.ts"; 10 | import * as C from "../const.ts"; 11 | import * as A from "../array.ts"; 12 | import { flow, identity, pipe } from "../fns.ts"; 13 | 14 | import { fromTraversable } from "./from_traversable.ts"; 15 | import { atRecord } from "./at.ts"; 16 | import { indexArray, indexRecord } from "./index.ts"; 17 | import { asTraversal as isoAsTraversal } from "./iso.ts"; 18 | import { asTraversal as prismAsTraversal, fromPredicate } from "./prism.ts"; 19 | import { asTraversal as optionalAsTraversal } from "./optional.ts"; 20 | import { 21 | asTraversal as lensAsTraversal, 22 | id as lensId, 23 | prop as lensProp, 24 | props as lensProps, 25 | } from "./lens.ts"; 26 | 27 | /******************************************************************************* 28 | * Types 29 | ******************************************************************************/ 30 | 31 | export type Traversal = { 32 | readonly traverse: ( 33 | A: TC.Applicative, 34 | ) => ( 35 | fata: (a: A) => Kind, 36 | ) => (s: S) => Kind; 37 | }; 38 | 39 | export type From = T extends Traversal ? S : never; 40 | 41 | export type To = T extends Traversal ? A : never; 42 | 43 | /******************************************************************************* 44 | * Constructors 45 | ******************************************************************************/ 46 | 47 | export { fromTraversable }; 48 | 49 | /******************************************************************************* 50 | * Pipeable Compose 51 | ******************************************************************************/ 52 | 53 | export const compose = (jk: Traversal) => 54 | (ij: Traversal): Traversal => ({ 55 | traverse: (F) => flow(jk.traverse(F), ij.traverse(F)), 56 | }); 57 | 58 | export const composeIso = flow(isoAsTraversal, compose); 59 | 60 | export const composeLens = flow(lensAsTraversal, compose); 61 | 62 | export const composePrism = flow(prismAsTraversal, compose); 63 | 64 | export const composeOptional = flow(optionalAsTraversal, compose); 65 | 66 | /******************************************************************************* 67 | * Pipeables 68 | ******************************************************************************/ 69 | 70 | export const id = (): Traversal => ({ 71 | traverse: () => identity, 72 | }); 73 | 74 | export const modify = (f: (a: A) => A) => 75 | (sa: Traversal) => pipe(f, sa.traverse(I.Applicative)); 76 | 77 | export const set = (a: A): ((sa: Traversal) => (s: S) => S) => { 78 | return modify(() => a); 79 | }; 80 | 81 | type FilterFn = { 82 | (refinement: Refinement): ( 83 | sa: Traversal, 84 | ) => Traversal; 85 | (predicate: Predicate): (sa: Traversal) => Traversal; 86 | }; 87 | 88 | export const filter: FilterFn = (predicate: Predicate) => 89 | pipe(fromPredicate(predicate), composePrism); 90 | 91 | export const prop = ( 92 | prop: P, 93 | ): ((sa: Traversal) => Traversal) => 94 | pipe(lensId(), lensProp(prop), composeLens); 95 | 96 | export const props = ( 97 | ...props: [P, P, ...Array

] 98 | ): ((sa: Traversal) => Traversal) => 99 | pipe(lensId(), lensProps(...props), composeLens); 100 | 101 | export const index = (i: number) => 102 | ( 103 | sa: Traversal>, 104 | ): Traversal => pipe(sa, composeOptional(indexArray().index(i))); 105 | 106 | export const key = (key: string) => 107 | ( 108 | sa: Traversal>>, 109 | ): Traversal => pipe(sa, composeOptional(indexRecord().index(key))); 110 | 111 | export const atKey = (key: string) => 112 | ( 113 | sa: Traversal>>, 114 | ): Traversal> => pipe(sa, composeLens(atRecord().at(key))); 115 | 116 | export const traverse = (T: TC.Traversable) => { 117 | const _traversal = fromTraversable(T); 118 | return ( 119 | sa: Traversal>, 120 | ): Traversal => 121 | pipe( 122 | sa, 123 | compose(_traversal()), 124 | ); 125 | }; 126 | 127 | export const foldMap = (M: TC.Monoid) => { 128 | const Applicative = C.getApplicative(M); 129 | return (fam: (a: A) => M) => 130 | (sa: Traversal): ((s: S) => M) => 131 | pipe(fam, sa.traverse(Applicative)); 132 | }; 133 | 134 | export const getAll = (sa: Traversal) => 135 | pipe((a: A) => [a], foldMap(A.getMonoid()))(sa); 136 | 137 | /******************************************************************************* 138 | * Pipeable Over ADT 139 | ******************************************************************************/ 140 | 141 | export const some: ( 142 | soa: Traversal>, 143 | ) => Traversal = composePrism({ 144 | getOption: identity, 145 | reverseGet: O.some, 146 | }); 147 | 148 | export const right: ( 149 | sea: Traversal>, 150 | ) => Traversal = composePrism({ 151 | getOption: E.getRight, 152 | reverseGet: E.right, 153 | }); 154 | 155 | export const left: ( 156 | sea: Traversal>, 157 | ) => Traversal = composePrism({ 158 | getOption: E.getLeft, 159 | reverseGet: E.left, 160 | }); 161 | -------------------------------------------------------------------------------- /ord.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file no-explicit-any 2 | 3 | import type { Ord } from "./type_classes.ts"; 4 | import type { Fn } from "./types.ts"; 5 | 6 | import { setoidStrict } from "./setoid.ts"; 7 | import { flow } from "./fns.ts"; 8 | 9 | /******************************************************************************* 10 | * Internal 11 | ******************************************************************************/ 12 | 13 | // lte for primmimtives 14 | const _lte = (a: any) => (b: any): boolean => a <= b; 15 | 16 | const _equals = setoidStrict.equals; 17 | 18 | /******************************************************************************* 19 | * Models 20 | ******************************************************************************/ 21 | 22 | export type Ordering = -1 | 0 | 1; 23 | 24 | export type Compare = Fn<[A, A], Ordering>; 25 | 26 | /******************************************************************************* 27 | * Module Instances 28 | ******************************************************************************/ 29 | 30 | export const ordString: Ord = { 31 | equals: _equals, 32 | lte: _lte, 33 | }; 34 | 35 | export const ordNumber: Ord = { 36 | equals: _equals, 37 | lte: _lte, 38 | }; 39 | 40 | export const ordBoolean: Ord = { 41 | equals: _equals, 42 | lte: _lte, 43 | }; 44 | 45 | /******************************************************************************* 46 | * Combinators 47 | ******************************************************************************/ 48 | 49 | export const compare = (O: Ord): Compare => 50 | (a, b) => O.lte(a)(b) ? O.equals(a)(b) ? 0 : -1 : 1; 51 | 52 | export const lt = (O: Ord) => 53 | (a: A) => (b: A): boolean => O.lte(a)(b) && !O.equals(a)(b); 54 | 55 | export const gt = (O: Ord) => (a: A) => (b: A): boolean => !O.lte(a)(b); 56 | 57 | export const lte = (O: Ord) => O.lte; 58 | 59 | export const gte = (O: Ord) => 60 | (a: A) => (b: A): boolean => !O.lte(a)(b) || O.equals(a)(b); 61 | 62 | export const eq = (O: Ord) => (a: A) => (b: A): boolean => O.equals(a)(b); 63 | 64 | export const min = (O: Ord) => (a: A) => (b: A): A => O.lte(a)(b) ? a : b; 65 | 66 | export const max = (O: Ord) => (a: A) => (b: A): A => O.lte(a)(b) ? b : a; 67 | 68 | export const clamp = (O: Ord) => 69 | (low: A, high: A): ((a: A) => A) => flow(max(O)(low), min(O)(high)); 70 | 71 | export const between = (O: Ord) => 72 | (low: A, high: A) => { 73 | const higher = lt(O)(low); 74 | const lower = gt(O)(high); 75 | 76 | return (a: A): boolean => lower(a) && higher(a); 77 | }; 78 | 79 | /******************************************************************************* 80 | * Combinator Getters 81 | ******************************************************************************/ 82 | 83 | export const getOrdUtilities = (O: Ord) => ({ 84 | lt: lt(O), 85 | gt: gt(O), 86 | lte: lte(O), 87 | gte: gte(O), 88 | eq: eq(O), 89 | min: min(O), 90 | max: max(O), 91 | clamp: clamp(O), 92 | compare: compare(O), 93 | }); 94 | -------------------------------------------------------------------------------- /reader.ts: -------------------------------------------------------------------------------- 1 | import type * as HKT from "./hkt.ts"; 2 | import type * as TC from "./type_classes.ts"; 3 | 4 | import { createDo } from "./derivations.ts"; 5 | import { constant, flow, identity, pipe } from "./fns.ts"; 6 | 7 | /******************************************************************************* 8 | * Types 9 | ******************************************************************************/ 10 | 11 | export type Reader = (r: R) => A; 12 | 13 | /******************************************************************************* 14 | * Kind Registration 15 | ******************************************************************************/ 16 | 17 | export const URI = "Reader"; 18 | 19 | export type URI = typeof URI; 20 | 21 | declare module "./hkt.ts" { 22 | // deno-lint-ignore no-explicit-any 23 | export interface Kinds<_ extends any[]> { 24 | [URI]: Reader<_[1], _[0]>; 25 | } 26 | } 27 | 28 | /******************************************************************************* 29 | * Constructors 30 | ******************************************************************************/ 31 | 32 | export const make: (r: R) => Reader = constant; 33 | 34 | export const ask: () => Reader = () => identity; 35 | 36 | export const asks: (f: (r: R) => A) => Reader = identity; 37 | 38 | /******************************************************************************* 39 | * Modules 40 | ******************************************************************************/ 41 | 42 | export const Functor: TC.Functor = { 43 | map: (fab) => (ta) => flow(ta, fab), 44 | }; 45 | 46 | export const Apply: TC.Apply = { 47 | ap: (tfai) => (ta) => (r) => pipe(ta(r), tfai(r)), 48 | map: Functor.map, 49 | }; 50 | 51 | export const Applicative: TC.Applicative = { 52 | of: constant, 53 | ap: Apply.ap, 54 | map: Functor.map, 55 | }; 56 | 57 | export const Chain: TC.Chain = { 58 | ap: Apply.ap, 59 | map: Functor.map, 60 | chain: (fatb) => (ta) => (r) => fatb(ta(r))(r), 61 | }; 62 | 63 | export const Monad: TC.Monad = { 64 | of: Applicative.of, 65 | ap: Apply.ap, 66 | map: Functor.map, 67 | join: (tta) => (r) => tta(r)(r), 68 | chain: Chain.chain, 69 | }; 70 | 71 | /******************************************************************************* 72 | * Pipeables 73 | ******************************************************************************/ 74 | 75 | export const { of, ap, map, join, chain } = Monad; 76 | 77 | /******************************************************************************* 78 | * Do 79 | ******************************************************************************/ 80 | 81 | export const { Do, bind, bindTo } = createDo(Monad); 82 | -------------------------------------------------------------------------------- /reader_either.ts: -------------------------------------------------------------------------------- 1 | import type * as HKT from "./hkt.ts"; 2 | import type * as TC from "./type_classes.ts"; 3 | import type { Lazy } from "./types.ts"; 4 | 5 | import * as E from "./either.ts"; 6 | import * as R from "./reader.ts"; 7 | import { createDo } from "./derivations.ts"; 8 | import { apply, constant, flow, identity, pipe } from "./fns.ts"; 9 | 10 | /******************************************************************************* 11 | * Types 12 | ******************************************************************************/ 13 | 14 | export type ReaderEither = R.Reader>; 15 | 16 | /******************************************************************************* 17 | * Kind Registration 18 | ******************************************************************************/ 19 | 20 | export const URI = "ReaderEither"; 21 | 22 | export type URI = typeof URI; 23 | 24 | declare module "./hkt.ts" { 25 | // deno-lint-ignore no-explicit-any 26 | export interface Kinds<_ extends any[]> { 27 | [URI]: ReaderEither<_[2], _[1], _[0]>; 28 | } 29 | } 30 | 31 | /******************************************************************************* 32 | * Constructors 33 | ******************************************************************************/ 34 | 35 | export const ask: () => ReaderEither = () => E.right; 36 | 37 | export const asks: ( 38 | f: (r: R) => A, 39 | ) => ReaderEither = (fra) => flow(fra, E.right); 40 | 41 | export const left = ( 42 | left: B, 43 | ): ReaderEither => R.of(E.left(left)); 44 | 45 | export const right = ( 46 | right: A, 47 | ): ReaderEither => R.of(E.right(right)); 48 | 49 | export const tryCatch = ( 50 | f: Lazy, 51 | onError: (e: unknown) => E, 52 | ): ReaderEither => { 53 | try { 54 | return R.of(E.right(f())); 55 | } catch (e) { 56 | return R.of(E.left(onError(e))); 57 | } 58 | }; 59 | 60 | export const fromEither = ( 61 | ta: E.Either, 62 | ): ReaderEither => R.of(ta); 63 | 64 | /******************************************************************************* 65 | * Modules 66 | ******************************************************************************/ 67 | 68 | export const Functor: TC.Functor = { 69 | map: (fab) => (ta) => flow(ta, E.map(fab)), 70 | }; 71 | 72 | export const Apply: TC.Apply = { 73 | ap: (tfab) => 74 | (ta) => (r) => pipe(tfab(r), E.chain((fab) => pipe(ta(r), E.map(fab)))), 75 | map: Functor.map, 76 | }; 77 | 78 | export const Applicative: TC.Applicative = { 79 | of: flow(E.right, constant), 80 | ap: Apply.ap, 81 | map: Functor.map, 82 | }; 83 | 84 | export const Chain: TC.Chain = { 85 | ap: Apply.ap, 86 | map: Functor.map, 87 | chain: (fatb) => R.chain(E.fold(left, fatb)), 88 | }; 89 | 90 | export const Monad: TC.Monad = { 91 | of: Applicative.of, 92 | ap: Apply.ap, 93 | map: Functor.map, 94 | join: Chain.chain(identity), 95 | chain: Chain.chain, 96 | }; 97 | 98 | export const Bifunctor: TC.Bifunctor = { 99 | bimap: (fai, fbj) => (tab) => flow(tab, E.bimap(fai, fbj)), 100 | mapLeft: (fbj) => (tab) => flow(tab, E.mapLeft(fbj)), 101 | }; 102 | 103 | export const MonadThrow: TC.MonadThrow = { 104 | of: Applicative.of, 105 | ap: Apply.ap, 106 | map: Functor.map, 107 | join: Monad.join, 108 | chain: Chain.chain, 109 | throwError: left, 110 | }; 111 | 112 | export const Alt: TC.Alt = { 113 | map: Monad.map, 114 | alt: (tb) => 115 | (ta) => 116 | (r) => 117 | pipe( 118 | ta(r), 119 | E.fold(() => tb(r), E.right), 120 | ), 121 | }; 122 | 123 | /******************************************************************************* 124 | * Pipeables 125 | ******************************************************************************/ 126 | 127 | export const { of, ap, map, join, chain, throwError } = MonadThrow; 128 | 129 | export const { bimap, mapLeft } = Bifunctor; 130 | 131 | export const { alt } = Alt; 132 | 133 | export const chainLeft = (fbtj: (b: B) => ReaderEither) => 134 | (ma: ReaderEither): ReaderEither => 135 | pipe(ma, R.chain(E.fold(fbtj, right))); 136 | 137 | export const compose = (rbc: ReaderEither) => 138 | (rab: ReaderEither): ReaderEither => 139 | flow(rab, E.chain(rbc)); 140 | 141 | export const widen: () => ( 142 | ta: ReaderEither, 143 | ) => ReaderEither = constant(identity); 144 | 145 | /******************************************************************************* 146 | * Do 147 | ******************************************************************************/ 148 | 149 | export const { Do, bind, bindTo } = createDo(Monad); 150 | -------------------------------------------------------------------------------- /record.ts: -------------------------------------------------------------------------------- 1 | import type * as HKT from "./hkt.ts"; 2 | import type * as TC from "./type_classes.ts"; 3 | import type { Fn } from "./types.ts"; 4 | 5 | import { hasOwnProperty, pipe } from "./fns.ts"; 6 | 7 | /******************************************************************************* 8 | * Types 9 | ******************************************************************************/ 10 | 11 | export type ReadonlyRecord = Readonly>; 12 | 13 | /******************************************************************************* 14 | * Kind Registration 15 | ******************************************************************************/ 16 | 17 | export const URI = "ReadonlyRecord"; 18 | 19 | export type URI = typeof URI; 20 | 21 | declare module "./hkt.ts" { 22 | // deno-lint-ignore no-explicit-any 23 | export interface Kinds<_ extends any[]> { 24 | [URI]: ReadonlyRecord<_[0]>; 25 | } 26 | } 27 | 28 | /******************************************************************************* 29 | * Optimizations 30 | ******************************************************************************/ 31 | 32 | export const _map = ( 33 | fab: (a: A, i: string) => B, 34 | as: { [K in KS]: A }, 35 | ): { [K in KS]: B } => { 36 | const keys = Object.keys(as) as KS[]; 37 | const out: Partial<{ [K in KS]: B }> = {}; 38 | for (let i = 0; i < keys.length; i++) { 39 | const key = keys[i]; 40 | out[key] = fab(as[key], key); 41 | } 42 | return out as { [K in KS]: B }; 43 | }; 44 | 45 | export const _reduce = ( 46 | faba: (b: B, a: A, i: string) => B, 47 | b: B, 48 | as: { [K in KS]: A }, 49 | ): B => { 50 | const keys = Object.keys(as) as KS[]; 51 | let out = b; 52 | for (let i = 0; i < keys.length; i++) { 53 | const key = keys[i]; 54 | out = faba(out, as[key], key); 55 | } 56 | return out; 57 | }; 58 | 59 | export const _assign = (i: KS) => 60 | (bs: R) => 61 | (b: R[typeof i]): Partial => { 62 | bs[i] = b; 63 | return bs; 64 | }; 65 | 66 | /******************************************************************************* 67 | * Modules 68 | ******************************************************************************/ 69 | 70 | export const Functor: TC.Functor = { 71 | map: (fai) => (ta) => _map(fai, ta), 72 | }; 73 | 74 | export const IndexedFunctor: TC.IndexedFunctor = { 75 | map: (fai) => (ta) => _map(fai, ta), 76 | }; 77 | 78 | export const IndexedFoldable: TC.IndexedFoldable< 79 | URI, 80 | string 81 | > = { 82 | reduce: (faba, a) => (tb) => _reduce(faba, a, tb), 83 | }; 84 | 85 | export const IndexedTraversable: TC.IndexedTraversable< 86 | URI, 87 | string 88 | > = { 89 | map: (fai) => (ta) => _map(fai, ta), 90 | reduce: IndexedFoldable.reduce, 91 | traverse: (A) => 92 | (faui) => 93 | (ta) => 94 | _reduce( 95 | (ubs, a, index) => 96 | pipe( 97 | faui(a, index), 98 | A.ap(pipe(ubs, A.map((is) => (i) => _assign(index)(is)(i)))), 99 | ), 100 | A.of({}), 101 | ta, 102 | ), 103 | }; 104 | 105 | export const Foldable: TC.Foldable = IndexedFoldable; 106 | 107 | export const Traversable = IndexedTraversable as TC.Traversable; 108 | 109 | /******************************************************************************* 110 | * Module Getters 111 | ******************************************************************************/ 112 | 113 | export const getShow = (SA: TC.Show): TC.Show> => ({ 114 | show: (ta) => 115 | `{${ 116 | Object.entries(ta).map(([key, value]) => `${key}: ${SA.show(value)}`) 117 | .join(", ") 118 | }}`, 119 | }); 120 | 121 | /******************************************************************************* 122 | * Pipeables 123 | ******************************************************************************/ 124 | 125 | export const { traverse, reduce, map } = Traversable; 126 | 127 | export const { 128 | traverse: indexedTraverse, 129 | reduce: indexedReduce, 130 | map: indexedMap, 131 | } = IndexedTraversable; 132 | 133 | export const insertAt = (k: K, a: A) => 134 | ( 135 | r: Record, 136 | ): Record => (r[k] === a ? r : { ...r, [k]: a }); 137 | 138 | export const deleteAt = (k: K) => 139 | ( 140 | r: Record, 141 | ): Record, A> => { 142 | if (!hasOwnProperty.call(r, k)) { 143 | return r; 144 | } 145 | const out = Object.assign({}, r); 146 | delete out[k]; 147 | return out; 148 | }; 149 | 150 | export const omit = ( 151 | props: [P, ...Array

], 152 | a: A, 153 | ): { [K in keyof A]: K extends P ? never : A[K] } => { 154 | const out: A = Object.assign({}, a); 155 | for (const k of props) { 156 | delete out[k]; 157 | } 158 | return out as { [K in keyof A]: K extends P ? never : A[K] }; 159 | }; 160 | 161 | export const pick = (...props: [K, K, ...K[]]) => 162 | (record: R): Pick => { 163 | const output: Partial> = {}; 164 | 165 | for (const k of props) { 166 | output[k] = record[k]; 167 | } 168 | 169 | return output as Pick; 170 | }; 171 | 172 | export const keys =

>(p: P): keyof P[] => 173 | (Object.keys(p) as unknown) as keyof P[]; 174 | 175 | export const zipFirst = (fabi: Fn<[string, A, unknown], I>) => 176 | (a: { [K in KS]: A }) => 177 | (b: Record): { [K in KS]: I } => 178 | _map((a, key) => fabi(key, a, b[key]), a); 179 | -------------------------------------------------------------------------------- /schemable/decode_error.ts: -------------------------------------------------------------------------------- 1 | import type { Semigroup } from "../type_classes.ts"; 2 | import { Free, getFreeSemigroup } from "../semigroup.ts"; 3 | import { flow } from "../fns.ts"; 4 | 5 | /******************************************************************************* 6 | * DecodeError 7 | * @from https://raw.githubusercontent.com/gcanti/io-ts/master/src/DecodeError.ts 8 | ******************************************************************************/ 9 | 10 | /******************************************************************************* 11 | * Types 12 | ******************************************************************************/ 13 | 14 | export const required = "required" as const; 15 | export const optional = "optional" as const; 16 | 17 | export type Kind = "required" | "optional"; 18 | 19 | export type Leaf = { 20 | readonly tag: "Leaf"; 21 | readonly actual: unknown; 22 | readonly error: E; 23 | }; 24 | 25 | export type Key = { 26 | readonly tag: "Key"; 27 | readonly key: string; 28 | readonly kind: Kind; 29 | readonly errors: Free>; 30 | }; 31 | 32 | export type Index = { 33 | readonly tag: "Index"; 34 | readonly index: number; 35 | readonly kind: Kind; 36 | readonly errors: Free>; 37 | }; 38 | 39 | export type Member = { 40 | readonly tag: "Member"; 41 | readonly index: number; 42 | readonly errors: Free>; 43 | }; 44 | 45 | export type Lazy = { 46 | readonly tag: "Lazy"; 47 | readonly id: string; 48 | readonly errors: Free>; 49 | }; 50 | 51 | export type Wrap = { 52 | readonly tag: "Wrap"; 53 | readonly error: E; 54 | readonly errors: Free>; 55 | }; 56 | 57 | export type DecodeError = 58 | | Leaf 59 | | Key 60 | | Index 61 | | Member 62 | | Lazy 63 | | Wrap; 64 | 65 | /******************************************************************************* 66 | * Constructors 67 | ******************************************************************************/ 68 | 69 | export const leaf = (actual: unknown, error: E): DecodeError => ({ 70 | tag: "Leaf", 71 | actual, 72 | error, 73 | }); 74 | 75 | export const key = ( 76 | key: string, 77 | kind: Kind, 78 | errors: Free>, 79 | ): DecodeError => ({ 80 | tag: "Key", 81 | key, 82 | kind, 83 | errors, 84 | }); 85 | 86 | export const index = ( 87 | index: number, 88 | kind: Kind, 89 | errors: Free>, 90 | ): DecodeError => ({ 91 | tag: "Index", 92 | index, 93 | kind, 94 | errors, 95 | }); 96 | 97 | export const member = ( 98 | index: number, 99 | errors: Free>, 100 | ): DecodeError => ({ 101 | tag: "Member", 102 | index, 103 | errors, 104 | }); 105 | 106 | export const lazy = ( 107 | id: string, 108 | errors: Free>, 109 | ): DecodeError => ({ 110 | tag: "Lazy", 111 | id, 112 | errors, 113 | }); 114 | 115 | export const wrap = ( 116 | error: E, 117 | errors: Free>, 118 | ): DecodeError => ({ 119 | tag: "Wrap", 120 | error, 121 | errors, 122 | }); 123 | 124 | /******************************************************************************* 125 | * Destructors 126 | ******************************************************************************/ 127 | 128 | export const fold = (patterns: { 129 | Leaf: (input: unknown, error: E) => R; 130 | Key: (key: string, kind: Kind, errors: Free>) => R; 131 | Index: (index: number, kind: Kind, errors: Free>) => R; 132 | Member: (index: number, errors: Free>) => R; 133 | Lazy: (id: string, errors: Free>) => R; 134 | Wrap: (error: E, errors: Free>) => R; 135 | }): ((e: DecodeError) => R) => { 136 | const f = (e: DecodeError): R => { 137 | switch (e.tag) { 138 | case "Leaf": 139 | return patterns.Leaf(e.actual, e.error); 140 | case "Key": 141 | return patterns.Key(e.key, e.kind, e.errors); 142 | case "Index": 143 | return patterns.Index(e.index, e.kind, e.errors); 144 | case "Member": 145 | return patterns.Member(e.index, e.errors); 146 | case "Lazy": 147 | return patterns.Lazy(e.id, e.errors); 148 | case "Wrap": 149 | return patterns.Wrap(e.error, e.errors); 150 | } 151 | }; 152 | return f; 153 | }; 154 | 155 | /******************************************************************************* 156 | * Module Getters 157 | ******************************************************************************/ 158 | 159 | export const getSemigroup = (): Semigroup>> => 160 | getFreeSemigroup(); 161 | 162 | export const make = { 163 | leaf: flow(leaf, Free.of), 164 | key: flow(key, Free.of), 165 | index: flow(index, Free.of), 166 | member: flow(member, Free.of), 167 | lazy: flow(lazy, Free.of), 168 | wrap: flow(wrap, Free.of), 169 | }; 170 | -------------------------------------------------------------------------------- /schemable/guard.ts: -------------------------------------------------------------------------------- 1 | import type * as HKT from "../hkt.ts"; 2 | 3 | import { isNil, isRecord } from "../fns.ts"; 4 | 5 | import * as S from "./schemable.ts"; 6 | 7 | /******************************************************************************* 8 | * Types 9 | ******************************************************************************/ 10 | 11 | export type Guard = (i: unknown) => i is A; 12 | 13 | export type TypeOf = D extends Guard ? A : never; 14 | 15 | /******************************************************************************* 16 | * Kind Registration 17 | ******************************************************************************/ 18 | 19 | export const URI = "Guard"; 20 | 21 | export type URI = typeof URI; 22 | 23 | declare module "../hkt.ts" { 24 | // deno-lint-ignore no-explicit-any 25 | export interface Kinds<_ extends any[]> { 26 | [URI]: Guard<_[0]>; 27 | } 28 | } 29 | 30 | /******************************************************************************* 31 | * Guard Schemables 32 | ******************************************************************************/ 33 | 34 | export const UnknownSchemable: S.UnknownSchemable = { 35 | unknown: () => (_: unknown): _ is unknown => true, 36 | }; 37 | 38 | export const StringSchemable: S.StringSchemable = { 39 | string: () => (u: unknown): u is string => typeof u === "string", 40 | }; 41 | 42 | export const NumberSchemable: S.NumberSchemable = { 43 | number: () => (u: unknown): u is number => typeof u === "number", 44 | }; 45 | 46 | export const BooleanSchemable: S.BooleanSchemable = { 47 | boolean: () => (u: unknown): u is boolean => typeof u === "boolean", 48 | }; 49 | 50 | export const LiteralSchemable: S.LiteralSchemable = { 51 | literal: (...s: A) => 52 | (u: unknown): u is A[number] => s.some((l) => l === u), 53 | }; 54 | 55 | export const NullableSchemable: S.NullableSchemable = { 56 | nullable: (or: Guard) => 57 | (u: unknown): u is A | null => u === null || or(u), 58 | }; 59 | 60 | export const UndefinableSchemable: S.UndefinableSchemable = { 61 | undefinable: (or: Guard) => 62 | (u: unknown): u is A | undefined => u === undefined || or(u), 63 | }; 64 | 65 | export const RecordSchemable: S.RecordSchemable = { 66 | record: (codomain: Guard) => 67 | (u: unknown): u is Record => 68 | isRecord(u) && Object.values(u).every(codomain), 69 | }; 70 | 71 | export const ArraySchemable: S.ArraySchemable = { 72 | array: (item: Guard) => 73 | (u: unknown): u is ReadonlyArray => Array.isArray(u) && u.every(item), 74 | }; 75 | 76 | export const TupleSchemable: S.TupleSchemable = { 77 | // deno-lint-ignore no-explicit-any 78 | tuple: (( 79 | ...components: { [K in keyof A]: Guard } 80 | ): Guard<{ [K in keyof A]: A[K] }> => 81 | (u: unknown): u is { [K in keyof A]: A[K] } => 82 | (Array.isArray(u) && components.length === u.length) && 83 | u.every((v, i) => components[i](v))) as S.TupleSchemable["tuple"], 84 | }; 85 | 86 | export const StructSchemable: S.StructSchemable = { 87 | struct: ( 88 | properties: { [K in keyof A]: Guard }, 89 | ): Guard<{ [K in keyof A]: A[K] }> => 90 | (u: unknown): u is { [K in keyof A]: A[K] } => (isRecord(u) && 91 | Object.keys(properties).every((key) => 92 | properties[key as keyof typeof properties](u[key]) 93 | )), 94 | }; 95 | 96 | export const PartialSchemable: S.PartialSchemable = { 97 | partial: ( 98 | properties: { [K in keyof A]: Guard }, 99 | ): Guard<{ [K in keyof A]?: A[K] | null }> => 100 | // deno-lint-ignore no-explicit-any 101 | (u: unknown): u is any => (isRecord(u) && 102 | Object.keys(properties).every((key) => 103 | isNil(u[key]) || properties[key as keyof typeof properties](u[key]) 104 | )), 105 | }; 106 | 107 | export const IntersectSchemable: S.IntersectSchemable = { 108 | intersect: (and: Guard) => 109 | (ga: Guard): Guard => 110 | (u: unknown): u is A & I => ga(u) && and(u), 111 | }; 112 | 113 | export const UnionSchemable: S.UnionSchemable = { 114 | union: (or: Guard) => 115 | (ga: Guard): Guard => 116 | (u: unknown): u is A | I => ga(u) || or(u), 117 | }; 118 | 119 | export const LazySchemable: S.LazySchemable = { 120 | lazy: (_: string, f: () => Guard) => f(), 121 | }; 122 | 123 | export const Schemable: S.Schemable = { 124 | ...UnknownSchemable, 125 | ...StringSchemable, 126 | ...NumberSchemable, 127 | ...BooleanSchemable, 128 | ...LiteralSchemable, 129 | ...NullableSchemable, 130 | ...UndefinableSchemable, 131 | ...RecordSchemable, 132 | ...ArraySchemable, 133 | ...TupleSchemable, 134 | ...StructSchemable, 135 | ...PartialSchemable, 136 | ...IntersectSchemable, 137 | ...UnionSchemable, 138 | ...LazySchemable, 139 | }; 140 | 141 | /******************************************************************************* 142 | * Pipeables 143 | ******************************************************************************/ 144 | 145 | export const unknown = Schemable.unknown(); 146 | 147 | export const string = Schemable.string(); 148 | 149 | export const number = Schemable.number(); 150 | 151 | export const boolean = Schemable.boolean(); 152 | 153 | export const literal = Schemable.literal; 154 | 155 | export const nullable = Schemable.nullable; 156 | 157 | export const undefinable = Schemable.undefinable; 158 | 159 | export const record = Schemable.record; 160 | 161 | export const array = Schemable.array; 162 | 163 | export const tuple = Schemable.tuple; 164 | 165 | export const struct = Schemable.struct; 166 | 167 | export const partial = Schemable.partial; 168 | 169 | export const intersect = Schemable.intersect; 170 | 171 | export const union = Schemable.union; 172 | 173 | export const lazy = Schemable.lazy; 174 | -------------------------------------------------------------------------------- /schemable/schemable.ts: -------------------------------------------------------------------------------- 1 | import type { Kind, URIS } from "../hkt.ts"; 2 | 3 | import { memoize } from "../fns.ts"; 4 | 5 | /******************************************************************************* 6 | * Types 7 | ******************************************************************************/ 8 | 9 | export type Literal = string | number | boolean | null; 10 | 11 | /******************************************************************************* 12 | * Schemable Type Class 13 | ******************************************************************************/ 14 | 15 | export type UnknownSchemable = { 16 | readonly unknown: () => Kind; 17 | }; 18 | 19 | export type StringSchemable = { 20 | readonly string: () => Kind; 21 | }; 22 | 23 | export type NumberSchemable = { 24 | readonly number: () => Kind; 25 | }; 26 | 27 | export type BooleanSchemable = { 28 | readonly boolean: () => Kind; 29 | }; 30 | 31 | export type LiteralSchemable = { 32 | readonly literal: ( 33 | ...s: A 34 | ) => Kind; 35 | }; 36 | 37 | export type NullableSchemable = { 38 | readonly nullable: (or: Kind) => Kind; 39 | }; 40 | 41 | export type UndefinableSchemable = { 42 | readonly undefinable: (or: Kind) => Kind; 43 | }; 44 | 45 | export type RecordSchemable = { 46 | readonly record: ( 47 | codomain: Kind, 48 | ) => Kind]>; 49 | }; 50 | 51 | export type ArraySchemable = { 52 | readonly array: (item: Kind) => Kind]>; 53 | }; 54 | 55 | export type TupleSchemable = { 56 | // deno-lint-ignore no-explicit-any 57 | readonly tuple: ( 58 | ...components: { [K in keyof A]: Kind } 59 | ) => Kind; 60 | }; 61 | 62 | export type StructSchemable = { 63 | readonly struct: ( 64 | properties: { [K in keyof A]: Kind }, 65 | ) => Kind; 66 | }; 67 | 68 | export type PartialSchemable = { 69 | readonly partial: ( 70 | properties: { [K in keyof A]: Kind }, 71 | ) => Kind; 72 | }; 73 | 74 | export type IntersectSchemable = { 75 | readonly intersect: (and: Kind) => ( 76 | ta: Kind, 77 | ) => Kind; 78 | }; 79 | 80 | export type UnionSchemable = { 81 | readonly union: ( 82 | or: Kind, 83 | ) => (ta: Kind) => Kind; 84 | }; 85 | 86 | export type LazySchemable = { 87 | readonly lazy: (id: string, f: () => Kind) => Kind; 88 | }; 89 | 90 | export type Schemable = 91 | & UnknownSchemable 92 | & StringSchemable 93 | & NumberSchemable 94 | & BooleanSchemable 95 | & LiteralSchemable 96 | & NullableSchemable 97 | & UndefinableSchemable 98 | & RecordSchemable 99 | & ArraySchemable 100 | & TupleSchemable 101 | & StructSchemable 102 | & PartialSchemable 103 | & IntersectSchemable 104 | & UnionSchemable 105 | & LazySchemable; 106 | 107 | export type Schema = (s: Schemable) => Kind; 108 | 109 | export type TypeOf = T extends Schema ? A : never; 110 | 111 | /******************************************************************************* 112 | * Utilities 113 | ******************************************************************************/ 114 | 115 | export const make = ( 116 | f: (s: Schemable) => Kind, 117 | // deno-lint-ignore no-explicit-any 118 | ): Schema => memoize(f) as any; 119 | -------------------------------------------------------------------------------- /semigroup.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file no-explicit-any 2 | 3 | import type { Ord, Semigroup } from "./type_classes.ts"; 4 | 5 | import { constant } from "./fns.ts"; 6 | 7 | /******************************************************************************* 8 | * Free Semigroup 9 | * @from https://raw.githubusercontent.com/gcanti/io-ts/master/src/FreeSemigroup.ts 10 | ******************************************************************************/ 11 | 12 | export type Of = { 13 | readonly tag: "Of"; 14 | readonly value: A; 15 | }; 16 | 17 | export type Concat = { 18 | readonly tag: "Concat"; 19 | readonly left: Free; 20 | readonly right: Free; 21 | }; 22 | 23 | export type Free = Of | Concat; 24 | 25 | export const Free = { 26 | of: (a: A): Free => ({ tag: "Of", value: a }), 27 | concat: (left: Free) => 28 | (right: Free): Free => ({ 29 | tag: "Concat", 30 | left, 31 | right, 32 | }), 33 | fold: ( 34 | onOf: (value: A) => R, 35 | onConcat: (left: Free, right: Free) => R, 36 | ) => 37 | (f: Free): R => { 38 | switch (f.tag) { 39 | case "Of": 40 | return onOf(f.value); 41 | case "Concat": 42 | return onConcat(f.left, f.right); 43 | } 44 | }, 45 | }; 46 | 47 | /******************************************************************************* 48 | * Module Instances 49 | ******************************************************************************/ 50 | 51 | export const semigroupAll: Semigroup = { 52 | concat: (x) => (y) => x && y, 53 | }; 54 | 55 | export const semigroupAny: Semigroup = { 56 | concat: (x) => (y) => x || y, 57 | }; 58 | 59 | export const semigroupSum: Semigroup = { 60 | concat: (x) => (y) => x + y, 61 | }; 62 | 63 | export const semigroupProduct: Semigroup = { 64 | concat: (x) => (y) => x * y, 65 | }; 66 | 67 | export const semigroupString: Semigroup = { 68 | concat: (x) => (y) => x + y, 69 | }; 70 | 71 | export const semigroupVoid: Semigroup = { 72 | concat: () => () => undefined, 73 | }; 74 | 75 | /******************************************************************************* 76 | * Module Getters 77 | ******************************************************************************/ 78 | 79 | export const getFreeSemigroup = (): Semigroup> => ({ 80 | concat: Free.concat, 81 | }); 82 | 83 | export const getFirstSemigroup = (): Semigroup => ({ 84 | concat: constant, 85 | }); 86 | 87 | export const getLastSemigroup = (): Semigroup => ({ 88 | concat: (_) => (y) => y, 89 | }); 90 | 91 | export const getTupleSemigroup = >>( 92 | ...semigroups: T 93 | ): Semigroup< 94 | { [K in keyof T]: T[K] extends Semigroup ? A : never } 95 | > => ({ 96 | concat: (x) => (y) => semigroups.map((s, i) => s.concat(x[i])(y[i])) as any, 97 | }); 98 | 99 | export const getDualSemigroup = (S: Semigroup): Semigroup => ({ 100 | concat: (x) => (y) => S.concat(y)(x), 101 | }); 102 | 103 | export const getStructSemigroup = >>( 104 | semigroups: { [K in keyof O]: Semigroup }, 105 | ): Semigroup => ({ 106 | concat: (x) => 107 | (y) => { 108 | const r: any = {}; 109 | for (const key of Object.keys(semigroups)) { 110 | r[key] = semigroups[key].concat(x[key])(y[key]); 111 | } 112 | return r; 113 | }, 114 | }); 115 | 116 | export const getMeetSemigroup = (O: Ord): Semigroup => ({ 117 | concat: (a) => (b) => (O.lte(a)(b) ? a : b), 118 | }); 119 | 120 | export const getJoinSemigroup = (O: Ord): Semigroup => ({ 121 | concat: (a) => (b) => (O.lte(a)(b) ? b : a), 122 | }); 123 | 124 | /******************************************************************************* 125 | * Pipeables 126 | ******************************************************************************/ 127 | 128 | export const fold = (S: Semigroup) => 129 | (startWith: A) => 130 | (as: ReadonlyArray): A => as.reduce((a, c) => S.concat(a)(c), startWith); 131 | -------------------------------------------------------------------------------- /sequence.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file no-explicit-any 2 | 3 | import type { Kind, URIS } from "./hkt.ts"; 4 | import type { Apply } from "./type_classes.ts"; 5 | import type { NonEmptyRecord } from "./types.ts"; 6 | 7 | import { pipe } from "./fns.ts"; 8 | 9 | /******************************************************************************* 10 | * Ap Function Constructors 11 | ******************************************************************************/ 12 | 13 | const loopTuple = (len: number, init: T[] = []): T[] | ((t: T) => any) => 14 | len === 0 ? init : (t: T) => loopTuple(len - 1, [...init, t]); 15 | 16 | const loopRecord = ( 17 | keys: K[], 18 | i = 0, 19 | init: Record = {}, 20 | ): Record | ((a: unknown) => any) => 21 | i === keys.length 22 | ? init 23 | : (a: unknown) => loopRecord(keys, i + 1, { ...init, [keys[i]]: a }); 24 | 25 | /******************************************************************************* 26 | * Sequence Tuple 27 | ******************************************************************************/ 28 | 29 | type NonEmptyArray = [T, ...T[]]; 30 | 31 | // deno-fmt-ignore 32 | type SequenceTuple>> = Kind ? A : never }, 34 | { [K in keyof R]: R[K] extends Kind ? B : never }[number], 35 | { [K in keyof R]: R[K] extends Kind ? C : never }[number], 36 | { [K in keyof R]: R[K] extends Kind ? D : never }[number] 37 | ]>; 38 | 39 | /** 40 | * Create a sequence over tuple function from Apply 41 | */ 42 | export const createSequenceTuple = (A: Apply) => 43 | >>( 44 | ...r: R 45 | ): SequenceTuple => { 46 | const [head, ...tail] = r; 47 | return tail.reduce( 48 | (acc: any, cur: any) => pipe(cur, A.ap(acc)) as any, 49 | pipe(head, A.map(loopTuple(r.length) as any) as any), 50 | ) as any; 51 | }; 52 | 53 | /******************************************************************************* 54 | * Sequence Struct 55 | ******************************************************************************/ 56 | 57 | // deno-fmt-ignore 58 | type SequenceStruct>> = Kind ? A : never }, 60 | { [K in keyof R]: R[K] extends Kind ? B : never }[keyof R], 61 | { [K in keyof R]: R[K] extends Kind ? C : never }[keyof R], 62 | { [K in keyof R]: R[K] extends Kind ? D : never }[keyof R] 63 | ]> 64 | 65 | export const createSequenceStruct = (A: Apply) => 66 | >>( 67 | r: NonEmptyRecord, 68 | ): SequenceStruct => { 69 | // Sort included to make apply ordering explicit 70 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort 71 | const keys: ((keyof R) & string)[] = Object.keys(r).sort(); 72 | const [head, ...tail] = keys; 73 | return tail.reduce( 74 | (f: any, key: keyof R) => pipe(r[key], A.ap(f) as any), 75 | pipe(r[head] as any, A.map(loopRecord(keys) as any) as any), 76 | ) as any; 77 | }; 78 | -------------------------------------------------------------------------------- /set.ts: -------------------------------------------------------------------------------- 1 | import type * as HKT from "./hkt.ts"; 2 | import type * as TC from "./type_classes.ts"; 3 | import type { Fn, Predicate } from "./types.ts"; 4 | 5 | import { pipe } from "./fns.ts"; 6 | import { _reduce } from "./array.ts"; 7 | import { fromEquals } from "./setoid.ts"; 8 | 9 | /******************************************************************************* 10 | * Kind Registration 11 | ******************************************************************************/ 12 | 13 | export const URI = "Set"; 14 | 15 | export type URI = typeof URI; 16 | 17 | declare module "./hkt.ts" { 18 | // deno-lint-ignore no-explicit-any 19 | export interface Kinds<_ extends any[]> { 20 | [URI]: Set<_[0]>; 21 | } 22 | } 23 | 24 | /******************************************************************************* 25 | * Constructors 26 | ******************************************************************************/ 27 | 28 | export const zero: Set = new Set(); 29 | 30 | export const empty = (): Set => new Set(); 31 | 32 | export const make = (...as: [A, ...A[]]): Set => new Set(as); 33 | 34 | /******************************************************************************* 35 | * Utilities 36 | ******************************************************************************/ 37 | 38 | export const some = ( 39 | predicate: Predicate, 40 | ) => 41 | (set: Set): boolean => { 42 | for (const a of set) { 43 | if (predicate(a)) { 44 | return true; 45 | } 46 | } 47 | return false; 48 | }; 49 | 50 | export const every = ( 51 | predicate: Predicate, 52 | ) => 53 | (set: Set): boolean => { 54 | for (const a of set) { 55 | if (!predicate(a)) { 56 | return false; 57 | } 58 | } 59 | return true; 60 | }; 61 | 62 | export const elem = (S: TC.Setoid) => (a: A) => some(S.equals(a)); 63 | 64 | export const elemOf = (S: TC.Setoid) => 65 | (set: Set) => (a: A) => elem(S)(a)(set); 66 | 67 | export const isSubset = (S: TC.Setoid) => 68 | (set: Set) => every(elemOf(S)(set)); 69 | 70 | export const union = (S: TC.Setoid) => 71 | (as: Set) => 72 | (bs: Set): Set => { 73 | const out = new Set(as); 74 | const isIn = elemOf(S)(out); 75 | for (const b of bs) { 76 | if (!isIn(b)) { 77 | out.add(b); 78 | } 79 | } 80 | return out; 81 | }; 82 | 83 | export const intersection = (S: TC.Setoid) => 84 | (ta: Set) => 85 | (tb: Set): Set => { 86 | const out = new Set(); 87 | const isIn = elemOf(S)(ta); 88 | for (const b of tb) { 89 | if (isIn(b)) { 90 | out.add(b); 91 | } 92 | } 93 | return out; 94 | }; 95 | 96 | export const compact = (S: TC.Setoid) => 97 | (ta: Set): Set => { 98 | const out = new Set(); 99 | const isIn = elemOf(S)(out); 100 | for (const a of ta) { 101 | if (!isIn(a)) { 102 | out.add(a); 103 | } 104 | } 105 | return out; 106 | }; 107 | 108 | export const join = (tta: Set>): Set => { 109 | const out = new Set(); 110 | for (const ta of tta) { 111 | for (const a of ta) { 112 | out.add(a); 113 | } 114 | } 115 | return out; 116 | }; 117 | 118 | /******************************************************************************* 119 | * Modules 120 | ******************************************************************************/ 121 | 122 | export const Functor: TC.Functor = { 123 | map: (fab: (a: A) => B) => 124 | (ta: Set): Set => { 125 | const out = new Set(); 126 | for (const a of ta) { 127 | out.add(fab(a)); 128 | } 129 | return out; 130 | }, 131 | }; 132 | 133 | export const Apply: TC.Apply = { 134 | ap: (tfab: Set<(a: A) => B>) => 135 | (ta: Set): Set => { 136 | const out = new Set(); 137 | for (const fab of tfab) { 138 | for (const a of ta) { 139 | out.add(fab(a)); 140 | } 141 | } 142 | return out; 143 | }, 144 | map: Functor.map, 145 | }; 146 | 147 | export const Filterable: TC.Filterable = { 148 | filter: (predicate: Predicate) => 149 | (ta: Set): Set => { 150 | const out: Set = new Set(); 151 | for (const a of ta) { 152 | if (predicate(a)) { 153 | out.add(a); 154 | } 155 | } 156 | return out; 157 | }, 158 | }; 159 | 160 | export const Foldable: TC.Foldable = { 161 | reduce: (faba: Fn<[A, B], A>, a: A) => 162 | (tb: Set): A => _reduce(Array.from(tb), faba, a), 163 | }; 164 | 165 | export const Traversable: TC.Traversable = { 166 | map: Functor.map, 167 | reduce: Foldable.reduce, 168 | traverse: (A) => 169 | (faub) => 170 | (ta) => 171 | pipe( 172 | ta, 173 | Foldable.reduce( 174 | (fbs, a) => 175 | pipe( 176 | faub(a), 177 | A.ap(pipe( 178 | fbs, 179 | A.map((bs) => 180 | (b) => { 181 | bs.add(b); 182 | return bs; 183 | } 184 | ), 185 | )), 186 | ), 187 | A.of(new Set()), 188 | ), 189 | ), 190 | }; 191 | 192 | /******************************************************************************* 193 | * Module Getters 194 | ******************************************************************************/ 195 | 196 | export const getShow = (S: TC.Show): TC.Show> => ({ 197 | show: (s) => `Set([${Array.from(s.values()).map(S.show).join(", ")}])`, 198 | }); 199 | 200 | export const getSetoid = (S: TC.Setoid): TC.Setoid> => { 201 | const subset = isSubset(S); 202 | return fromEquals((x) => (y) => subset(x)(y) && subset(y)(x)); 203 | }; 204 | 205 | export const getUnionMonoid = (S: TC.Setoid): TC.Monoid> => ({ 206 | concat: union(S), 207 | empty, 208 | }); 209 | 210 | /******************************************************************************* 211 | * Pipeables 212 | ******************************************************************************/ 213 | 214 | export const { filter } = Filterable; 215 | 216 | export const { map, reduce, traverse } = Traversable; 217 | -------------------------------------------------------------------------------- /setoid.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file no-explicit-any 2 | 3 | import type { Setoid } from "./type_classes.ts"; 4 | 5 | /******************************************************************************* 6 | * Constructors 7 | ******************************************************************************/ 8 | 9 | export const fromEquals = ( 10 | equals: (x: A) => (y: A) => boolean, 11 | ): Setoid => ({ 12 | equals: (x) => (y) => x === y || equals(x)(y), 13 | }); 14 | 15 | /******************************************************************************* 16 | * Module Instances 17 | ******************************************************************************/ 18 | 19 | export const setoidStrict: Setoid = { 20 | equals: (a) => (b) => a === b, 21 | }; 22 | 23 | export const setoidString: Setoid = setoidStrict; 24 | 25 | export const setoidNumber: Setoid = setoidStrict; 26 | 27 | export const setoidBoolean: Setoid = setoidStrict; 28 | 29 | export const setoidDate: Setoid = { 30 | equals: (x) => (y) => x.valueOf() === y.valueOf(), 31 | }; 32 | 33 | /******************************************************************************* 34 | * Module Getters 35 | ******************************************************************************/ 36 | 37 | export const getStructSetoid = >>( 38 | eqs: { [K in keyof O]: Setoid }, 39 | ): Setoid => 40 | fromEquals((x) => 41 | (y) => { 42 | for (const k in eqs) { 43 | if (!eqs[k].equals(x[k])(y[k])) { 44 | return false; 45 | } 46 | } 47 | return true; 48 | } 49 | ); 50 | 51 | export const getTupleSetoid = >>( 52 | ...eqs: T 53 | ): Setoid<{ [K in keyof T]: T[K] extends Setoid ? A : never }> => 54 | fromEquals((x) => (y) => eqs.every((E, i) => E.equals(x[i])(y[i]))); 55 | -------------------------------------------------------------------------------- /state.ts: -------------------------------------------------------------------------------- 1 | import type * as HKT from "./hkt.ts"; 2 | import type * as TC from "./type_classes.ts"; 3 | 4 | import { createDo } from "./derivations.ts"; 5 | import { flow, identity } from "./fns.ts"; 6 | 7 | /******************************************************************************* 8 | * Types 9 | ******************************************************************************/ 10 | 11 | export type State = (s: S) => [A, S]; 12 | 13 | /******************************************************************************* 14 | * Kind Registration 15 | ******************************************************************************/ 16 | 17 | export const URI = "State"; 18 | 19 | export type URI = typeof URI; 20 | 21 | declare module "./hkt.ts" { 22 | // deno-lint-ignore no-explicit-any 23 | export interface Kinds<_ extends any[]> { 24 | [URI]: State<_[1], _[0]>; 25 | } 26 | } 27 | 28 | /******************************************************************************* 29 | * Constructors 30 | ******************************************************************************/ 31 | 32 | export const get = (): State => (s: S) => [s, s]; 33 | 34 | export const gets = (fsa: (s: S) => A): State => 35 | (s: S) => [fsa(s), s]; 36 | 37 | export const put = (s: S): State => () => [undefined, s]; 38 | 39 | export const modify = (fss: (s: S) => S): State => 40 | (s: S) => [undefined, fss(s)]; 41 | 42 | export const make = (a: A, s: S): State => () => [a, s]; 43 | 44 | /******************************************************************************* 45 | * Modules 46 | ******************************************************************************/ 47 | 48 | export const Functor: TC.Functor = { 49 | map: (fab) => (ta) => flow(ta, ([a, s]) => [fab(a), s]), 50 | }; 51 | 52 | export const Apply: TC.Apply = { 53 | ap: (tfab) => 54 | (ta) => 55 | (s1) => { 56 | const [fab, s2] = tfab(s1); 57 | const [a, s3] = ta(s2); 58 | return [fab(a), s3]; 59 | }, 60 | map: Functor.map, 61 | }; 62 | 63 | export const Applicative: TC.Applicative = { 64 | of: (a) => (s) => [a, s], 65 | ap: Apply.ap, 66 | map: Functor.map, 67 | }; 68 | 69 | export const Chain: TC.Chain = { 70 | ap: Apply.ap, 71 | map: Functor.map, 72 | chain: (fatb) => (ta) => flow(ta, ([a, s]) => fatb(a)(s)), 73 | }; 74 | 75 | export const Monad: TC.Monad = { 76 | of: Applicative.of, 77 | ap: Apply.ap, 78 | map: Functor.map, 79 | join: Chain.chain(identity), 80 | chain: Chain.chain, 81 | }; 82 | 83 | /******************************************************************************* 84 | * Pipeables 85 | ******************************************************************************/ 86 | 87 | export const { of, ap, map, join, chain } = Monad; 88 | 89 | export const evaluate = (s: S) => (ma: State): A => ma(s)[0]; 90 | 91 | export const execute = (s: S) => (ma: State): S => ma(s)[1]; 92 | 93 | /******************************************************************************* 94 | * Do 95 | ******************************************************************************/ 96 | 97 | export const { Do, bind, bindTo } = createDo(Monad); 98 | -------------------------------------------------------------------------------- /task.ts: -------------------------------------------------------------------------------- 1 | import type * as HKT from "./hkt.ts"; 2 | import type * as TC from "./type_classes.ts"; 3 | import { Lazy } from "./types.ts"; 4 | 5 | import { apply, flow, wait, identity } from "./fns.ts"; 6 | import { createDo } from "./derivations.ts"; 7 | 8 | /******************************************************************************* 9 | * Types 10 | ******************************************************************************/ 11 | 12 | export type Task = () => Promise; 13 | 14 | /******************************************************************************* 15 | * Kind Registration 16 | ******************************************************************************/ 17 | 18 | export const URI = "Task"; 19 | 20 | export type URI = typeof URI; 21 | 22 | declare module "./hkt.ts" { 23 | // deno-lint-ignore no-explicit-any 24 | export interface Kinds<_ extends any[]> { 25 | [URI]: Task<_[0]>; 26 | } 27 | } 28 | 29 | /******************************************************************************* 30 | * Combinators 31 | ******************************************************************************/ 32 | 33 | export const make = (a: A): Task => () => Promise.resolve(a); 34 | 35 | export const delay = (ms: number) => 36 | (ma: Task): Task => () => wait(ms).then(ma); 37 | 38 | export const fromThunk = (fa: Lazy): Task => 39 | () => Promise.resolve(fa()); 40 | 41 | export const tryCatch = (fa: Lazy, onError: (e: unknown) => A): Task => 42 | () => { 43 | try { 44 | return Promise.resolve(fa()); 45 | } catch (e) { 46 | return Promise.resolve(onError(e)); 47 | } 48 | }; 49 | 50 | /******************************************************************************* 51 | * Modules (Parallel) 52 | ******************************************************************************/ 53 | 54 | export const Functor: TC.Functor = { 55 | map: (fab) => (ta) => () => ta().then(fab), 56 | }; 57 | 58 | export const Apply: TC.Apply = { 59 | ap: (tfab) => 60 | (ta) => () => Promise.all([tfab(), ta()]).then(([f, a]) => f(a)), 61 | map: Functor.map, 62 | }; 63 | 64 | export const Applicative: TC.Applicative = { 65 | of: (a) => () => Promise.resolve(a), 66 | ap: Apply.ap, 67 | map: Functor.map, 68 | }; 69 | 70 | export const Chain: TC.Chain = { 71 | ap: Apply.ap, 72 | map: Functor.map, 73 | chain: (fatb) => (ta) => () => ta().then(flow(fatb, apply())), 74 | }; 75 | 76 | export const Monad: TC.Monad = { 77 | of: Applicative.of, 78 | ap: Apply.ap, 79 | map: Functor.map, 80 | join: Chain.chain(identity), 81 | chain: Chain.chain, 82 | }; 83 | 84 | /******************************************************************************* 85 | * Modules (Sequential) 86 | ******************************************************************************/ 87 | 88 | export const ApplySeq: TC.Apply = { 89 | ap: (tfab) => (ta) => async () => (await tfab())(await ta()), 90 | map: Functor.map, 91 | }; 92 | 93 | export const MonadSeq: TC.Monad = { 94 | of: Applicative.of, 95 | ap: ApplySeq.ap, 96 | map: Functor.map, 97 | join: Chain.chain(identity), 98 | chain: Chain.chain, 99 | }; 100 | 101 | /******************************************************************************* 102 | * Pipeables 103 | ******************************************************************************/ 104 | 105 | export const { of, ap, map, join, chain } = Monad; 106 | 107 | export const { ap: apSeq } = ApplySeq; 108 | 109 | /******************************************************************************* 110 | * Do Notation 111 | ******************************************************************************/ 112 | 113 | export const { Do, bind, bindTo } = createDo(Monad); 114 | -------------------------------------------------------------------------------- /task_either.ts: -------------------------------------------------------------------------------- 1 | import type * as HKT from "./hkt.ts"; 2 | import type * as TC from "./type_classes.ts"; 3 | import type { Lazy } from "./types.ts"; 4 | 5 | import * as E from "./either.ts"; 6 | import * as T from "./task.ts"; 7 | import { createDo } from "./derivations.ts"; 8 | import { constant, flow, identity, pipe, wait } from "./fns.ts"; 9 | 10 | /******************************************************************************* 11 | * Types 12 | ******************************************************************************/ 13 | 14 | export type TaskEither = T.Task>; 15 | 16 | /******************************************************************************* 17 | * Kind Registration 18 | ******************************************************************************/ 19 | 20 | export const URI = "TaskEither"; 21 | 22 | export type URI = typeof URI; 23 | 24 | declare module "./hkt.ts" { 25 | // deno-lint-ignore no-explicit-any 26 | export interface Kinds<_ extends any[]> { 27 | [URI]: TaskEither<_[1], _[0]>; 28 | } 29 | } 30 | 31 | /******************************************************************************* 32 | * Constructors 33 | ******************************************************************************/ 34 | 35 | export const left = (left: E): TaskEither => 36 | T.of(E.left(left)); 37 | 38 | export const right = (right: A): TaskEither => 39 | T.of(E.right(right)); 40 | 41 | export const tryCatch = ( 42 | f: Lazy, 43 | onError: (e: unknown) => E, 44 | ): TaskEither => 45 | () => { 46 | try { 47 | return Promise.resolve(E.right(f())); 48 | } catch (e) { 49 | return Promise.resolve(E.left(onError(e))); 50 | } 51 | }; 52 | 53 | export const fromFailableTask = (onError: (e: unknown) => E) => 54 | (ta: T.Task): TaskEither => 55 | () => ta().then(E.right).catch((e) => E.left(onError(e))); 56 | 57 | export const fromEither = (ta: E.Either): TaskEither => 58 | pipe(ta, E.fold((e) => left(e), right)); 59 | 60 | /******************************************************************************* 61 | * Utilities 62 | ******************************************************************************/ 63 | 64 | export const then = (fab: (a: A) => B) => 65 | (p: Promise): Promise => p.then(fab); 66 | 67 | /******************************************************************************* 68 | * Modules (Parallel) 69 | ******************************************************************************/ 70 | 71 | export const Functor: TC.Functor = { 72 | map: (fai) => (ta) => flow(ta, then(E.map(fai))), 73 | }; 74 | 75 | export const Bifunctor: TC.Bifunctor = { 76 | bimap: (fab, fcd) => (tac) => () => tac().then(E.bimap(fab, fcd)), 77 | mapLeft: (fef) => (ta) => pipe(ta, Bifunctor.bimap(fef, identity)), 78 | }; 79 | 80 | export const Apply: TC.Apply = { 81 | ap: flow(T.map(E.ap), T.ap), 82 | map: Functor.map, 83 | }; 84 | 85 | export const Applicative: TC.Applicative = { 86 | of: right, 87 | ap: Apply.ap, 88 | map: Functor.map, 89 | }; 90 | 91 | export const Chain: TC.Chain = { 92 | ap: Apply.ap, 93 | map: Functor.map, 94 | chain: (fatb) => 95 | (ta) => 96 | async () => { 97 | const ea = await ta(); 98 | if (E.isLeft(ea)) { 99 | return ea; 100 | } 101 | return await fatb(ea.right)(); 102 | }, 103 | }; 104 | 105 | export const Monad: TC.Monad = { 106 | of: Applicative.of, 107 | ap: Apply.ap, 108 | map: Functor.map, 109 | join: Chain.chain(identity), 110 | chain: Chain.chain, 111 | }; 112 | 113 | export const MonadThrow: TC.MonadThrow = { 114 | of: Applicative.of, 115 | ap: Apply.ap, 116 | map: Functor.map, 117 | join: Monad.join, 118 | chain: Chain.chain, 119 | throwError: left, 120 | }; 121 | 122 | export const Alt: TC.Alt = ({ 123 | map: Functor.map, 124 | alt: (tb) => (ta) => () => ta().then((te) => E.isLeft(te) ? tb() : te), 125 | }); 126 | 127 | /******************************************************************************* 128 | * Modules (Sequential) 129 | ******************************************************************************/ 130 | 131 | // TODO Refactor in terms of Task.ap (Sequential) 132 | export const ApplySeq: TC.Apply = { 133 | ap: (tfab) => 134 | (ta) => 135 | async () => { 136 | const efab = await tfab(); 137 | const ea = await ta(); 138 | return pipe(ea, E.ap(efab)); 139 | }, 140 | map: Functor.map, 141 | }; 142 | 143 | export const ApplicativeSeq: TC.Applicative = { 144 | of: right, 145 | ap: ApplySeq.ap, 146 | map: Functor.map, 147 | }; 148 | 149 | export const ChainSeq: TC.Chain = { 150 | ap: ApplySeq.ap, 151 | map: Functor.map, 152 | chain: Chain.chain, 153 | }; 154 | 155 | export const MonadSeq: TC.Monad = { 156 | of: ApplicativeSeq.of, 157 | ap: ApplySeq.ap, 158 | map: Functor.map, 159 | join: ChainSeq.chain(identity), 160 | chain: ChainSeq.chain, 161 | }; 162 | 163 | export const MonadThrowSeq: TC.MonadThrow = { 164 | of: ApplicativeSeq.of, 165 | ap: ApplySeq.ap, 166 | map: Functor.map, 167 | join: MonadSeq.join, 168 | chain: ChainSeq.chain, 169 | throwError: left, 170 | }; 171 | 172 | /******************************************************************************* 173 | * Pipeables 174 | ******************************************************************************/ 175 | 176 | export const { of, ap, map, join, chain } = Monad; 177 | 178 | export const { bimap, mapLeft } = Bifunctor; 179 | 180 | export const { ap: apSeq } = ApplySeq; 181 | 182 | export const chainLeft = (onLeft: (e: E) => TaskEither) => 183 | T.chain(E.fold>(onLeft, right)); 184 | 185 | export const widen: () => ( 186 | ta: TaskEither, 187 | ) => TaskEither = constant(identity); 188 | 189 | // This leaks async ops so we cut it for now. 190 | //export const timeout = (ms: number, onTimeout: () => E) => 191 | // (ta: TaskEither): TaskEither => 192 | // () => Promise.race([ta(), wait(ms).then(flow(onTimeout, E.left))]); 193 | 194 | /******************************************************************************* 195 | * Do Notation 196 | ******************************************************************************/ 197 | 198 | export const { Do, bind, bindTo } = createDo(Monad); 199 | -------------------------------------------------------------------------------- /testing/const.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; 2 | 3 | import type * as HKT from "../hkt.ts"; 4 | 5 | import * as C from "../const.ts"; 6 | import { setoidBoolean } from "../setoid.ts"; 7 | import { ordNumber } from "../ord.ts"; 8 | import { semigroupSum } from "../semigroup.ts"; 9 | import { monoidSum } from "../monoid.ts"; 10 | import { pipe } from "../fns.ts"; 11 | 12 | import * as AS from "./assert.ts"; 13 | 14 | Deno.test("Const make", () => { 15 | assertEquals(C.make(1), 1); 16 | }); 17 | 18 | Deno.test("Const getShow", () => { 19 | const show = C.getShow({ show: (n: number) => n.toString() }); 20 | 21 | assertEquals(show.show(C.make(1)), "Const(1)"); 22 | }); 23 | 24 | Deno.test("Const getSetoid", () => { 25 | const setoid = C.getSetoid(setoidBoolean); 26 | 27 | assertEquals(setoid.equals(C.make(true))(C.make(true)), true); 28 | assertEquals(setoid.equals(C.make(false))(C.make(true)), false); 29 | }); 30 | 31 | Deno.test("Const getOrd", () => { 32 | const ord = C.getOrd(ordNumber); 33 | 34 | assertEquals(ord.lte(C.make(1))(C.make(1)), true); 35 | assertEquals(ord.lte(C.make(2))(C.make(1)), false); 36 | assertEquals(ord.lte(C.make(0))(C.make(1)), true); 37 | }); 38 | 39 | Deno.test("Const getSemigroup", () => { 40 | const semigroup = C.getSemigroup(semigroupSum); 41 | 42 | assertEquals(semigroup.concat(C.make(1))(C.make(1)), 2); 43 | }); 44 | 45 | Deno.test("Const getApply", () => { 46 | const apply = C.getApply(monoidSum); 47 | 48 | assertEquals(pipe(C.make(1), apply.ap(1)), 2); 49 | assertEquals(pipe(C.make(1), apply.map((n: number) => n + 1)), 1); 50 | }); 51 | 52 | Deno.test("Const getApplicative", () => { 53 | const applicative = C.getApplicative(monoidSum); 54 | 55 | assertEquals(pipe(C.make(1), applicative.ap(1)), 2); 56 | assertEquals(pipe(C.make(1), applicative.map((n: number) => n + 1)), 1); 57 | assertEquals(applicative.of(1), 0); 58 | }); 59 | 60 | Deno.test("Const Functor", () => { 61 | AS.assertFunctor(C.Functor, { 62 | ta: C.make(1) as never, 63 | fai: (n: number) => n + 1, 64 | fij: (n: number) => 2 * n, 65 | }); 66 | }); 67 | 68 | Deno.test("Const Contravariant", () => { 69 | AS.assertContravariant(C.Contravariant, { 70 | ti: C.make(1) as never, 71 | tj: C.make(2) as never, 72 | fai: (n: number) => n + 1, 73 | fij: (n: number) => n + 2, 74 | }); 75 | }); 76 | 77 | Deno.test("Const Bifunctor", () => { 78 | AS.assertBifunctor(C.Bifunctor, { 79 | tab: C.make(2) as C.Const, 80 | fai: (n: number) => n + 1, 81 | fij: (n: number) => n + 2, 82 | fbx: (n: number) => n + 1, 83 | fxy: (n: number) => n + 2, 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /testing/fns.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | assertEquals, 3 | assertThrows, 4 | } from "https://deno.land/std/testing/asserts.ts"; 5 | 6 | import * as F from "../fns.ts"; 7 | 8 | Deno.test("fns isNotNil", () => { 9 | assertEquals(F.isNotNil(undefined), false); 10 | assertEquals(F.isNotNil(null), false); 11 | assertEquals(F.isNotNil(1), true); 12 | assertEquals(F.isNotNil(0), true); 13 | assertEquals(F.isNotNil("Hello"), true); 14 | assertEquals(F.isNotNil(""), true); 15 | }); 16 | 17 | Deno.test("fns isNil", () => { 18 | assertEquals(F.isNil(undefined), true); 19 | assertEquals(F.isNil(null), true); 20 | assertEquals(F.isNil(1), false); 21 | assertEquals(F.isNil(0), false); 22 | assertEquals(F.isNil("Hello"), false); 23 | assertEquals(F.isNil(""), false); 24 | }); 25 | 26 | Deno.test("fns isRecord", () => { 27 | assertEquals(F.isRecord(null), false); 28 | assertEquals(F.isRecord(undefined), false); 29 | assertEquals(F.isRecord(0), false); 30 | assertEquals(F.isRecord({}), true); 31 | assertEquals(F.isRecord({ a: 1 }), true); 32 | }); 33 | 34 | Deno.test("fns identity", () => { 35 | [0, "hello", undefined, null, {}].forEach((v) => { 36 | assertEquals(F.identity(v), v); 37 | }); 38 | }); 39 | 40 | Deno.test("fns compose", () => { 41 | const fab = (n: number) => n + 1; 42 | const comp0 = F.compose(fab); 43 | const comp1 = comp0(fab); 44 | assertEquals(comp1(1), 3); 45 | }); 46 | 47 | Deno.test("fns constant", () => { 48 | [0, "hello", undefined, null, {}].forEach((v) => { 49 | const lazy = F.constant(v); 50 | assertEquals(lazy(), v); 51 | }); 52 | }); 53 | 54 | Deno.test("fns memoize", () => { 55 | let count = 0; 56 | const fn = (n: number) => { 57 | if (count > 0) { 58 | throw new Error("Should not throw"); 59 | } 60 | count += 1; 61 | return n; 62 | }; 63 | const memo = F.memoize(fn); 64 | assertEquals(memo(1), 1); 65 | assertEquals(memo(1), 1); 66 | assertEquals(memo(1), 1); 67 | }); 68 | 69 | Deno.test("fns typeof", () => { 70 | assertEquals(F.typeOf("Hello"), "string"); 71 | assertEquals(F.typeOf(0), "number"); 72 | assertEquals(F.typeOf(BigInt(0)), "bigint"); 73 | assertEquals(F.typeOf(true), "boolean"); 74 | assertEquals(F.typeOf(undefined), "undefined"); 75 | assertEquals(F.typeOf({}), "object"); 76 | assertEquals(F.typeOf((n: number) => n + 1), "function"); 77 | assertEquals(F.typeOf(null), "null"); 78 | }); 79 | 80 | Deno.test("fns intesrsect", () => { 81 | const a = { a: 1 }; 82 | const b = { b: 2 }; 83 | const c = { a: 2, b: 2 }; 84 | const ab = { a: 1, b: 2 }; 85 | const ac = { a: 2, b: 2 }; 86 | assertEquals(F.intersect(a, b), ab); 87 | assertEquals(F.intersect(a, c), ac); 88 | assertEquals(F.intersect(null, b), b); 89 | assertEquals(F.intersect(b, null), b); 90 | }); 91 | 92 | Deno.test("fns hasOwnProperty", () => { 93 | assertEquals(F.hasOwnProperty("a"), false); 94 | }); 95 | 96 | Deno.test("fns apply", () => { 97 | const fab = (n: number) => n + 1; 98 | const apply = F.apply(1); 99 | assertEquals(apply(fab), 2); 100 | }); 101 | 102 | Deno.test("fns call", () => { 103 | const fab = (n: number) => n + 1; 104 | const call = F.call(fab); 105 | assertEquals(call(1), 2); 106 | }); 107 | 108 | Deno.test("fns apply1", () => { 109 | const fab = (n: number) => n + 1; 110 | assertEquals(F.apply1(1, fab), 2); 111 | }); 112 | 113 | Deno.test("fns absurd", () => { 114 | assertThrows(() => F.absurd(null as never)); 115 | }); 116 | 117 | Deno.test("fns _", () => { 118 | assertThrows(F._); 119 | }); 120 | 121 | Deno.test("fns wait", async () => { 122 | const within = (high: number, low: number) => 123 | (value: number): boolean => value >= low && value <= high; 124 | const target = 100; 125 | const high = 900; // github actions on macos tend to drag 126 | const low = 50; 127 | 128 | const test = within(high, low); 129 | const start = Date.now(); 130 | const result = await F.wait(target).then(() => 1); 131 | const end = Date.now(); 132 | 133 | const diff = end - start; 134 | 135 | assertEquals(result, 1); 136 | assertEquals( 137 | test(diff), 138 | true, 139 | `wait of ${target}ms took ${diff}ms. Acceptable range ${low}-${high}ms`, 140 | ); 141 | }); 142 | 143 | Deno.test("fns pipe", () => { 144 | const fab = (n: number) => n + 1; 145 | 146 | const r0 = F.pipe(0); 147 | const r1 = F.pipe(0, fab); 148 | const r2 = F.pipe(0, fab, fab); 149 | const r3 = F.pipe(0, fab, fab, fab); 150 | const r4 = F.pipe(0, fab, fab, fab, fab); 151 | const r5 = F.pipe(0, fab, fab, fab, fab, fab); 152 | const r6 = F.pipe(0, fab, fab, fab, fab, fab, fab); 153 | const r7 = F.pipe(0, fab, fab, fab, fab, fab, fab, fab); 154 | const r8 = F.pipe(0, fab, fab, fab, fab, fab, fab, fab, fab); 155 | const r9 = F.pipe(0, fab, fab, fab, fab, fab, fab, fab, fab, fab); 156 | 157 | assertEquals([r0, r1, r2, r3, r4, r5, r6, r7, r8, r9], [ 158 | 0, 159 | 1, 160 | 2, 161 | 3, 162 | 4, 163 | 5, 164 | 6, 165 | 7, 166 | 8, 167 | 9, 168 | ]); 169 | }); 170 | 171 | Deno.test("fns flow", () => { 172 | const fab = (n: number) => n + 1; 173 | 174 | const r1 = F.flow(fab); 175 | const r2 = F.flow(fab, fab); 176 | const r3 = F.flow(fab, fab, fab); 177 | const r4 = F.flow(fab, fab, fab, fab); 178 | const r5 = F.flow(fab, fab, fab, fab, fab); 179 | const r6 = F.flow(fab, fab, fab, fab, fab, fab); 180 | const r7 = F.flow(fab, fab, fab, fab, fab, fab, fab); 181 | const r8 = F.flow(fab, fab, fab, fab, fab, fab, fab, fab); 182 | const r9 = F.flow(fab, fab, fab, fab, fab, fab, fab, fab, fab); 183 | 184 | assertEquals(r1(0), 1); 185 | assertEquals(r2(0), 2); 186 | assertEquals(r3(0), 3); 187 | assertEquals(r4(0), 4); 188 | assertEquals(r5(0), 5); 189 | assertEquals(r6(0), 6); 190 | assertEquals(r7(0), 7); 191 | assertEquals(r8(0), 8); 192 | assertEquals(r9(0), 9); 193 | }); 194 | -------------------------------------------------------------------------------- /testing/identity.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; 2 | 3 | import * as I from "../identity.ts"; 4 | 5 | import * as AS from "./assert.ts"; 6 | 7 | Deno.test("Identity Functor", () => { 8 | AS.assertFunctor(I.Functor, { ta: I.of(1), fai: AS.add, fij: AS.multiply }); 9 | }); 10 | 11 | Deno.test("Identity Apply", () => { 12 | AS.assertApply(I.Apply, { 13 | ta: I.of(1), 14 | fai: AS.add, 15 | fij: AS.multiply, 16 | tfai: I.of(AS.add), 17 | tfij: I.of(AS.multiply), 18 | }); 19 | }); 20 | 21 | Deno.test("Identity Applicative", () => { 22 | AS.assertApplicative(I.Applicative, { 23 | a: 1, 24 | ta: I.of(1), 25 | fai: AS.add, 26 | fij: AS.multiply, 27 | tfai: I.of(AS.add), 28 | tfij: I.of(AS.multiply), 29 | }); 30 | }); 31 | 32 | Deno.test("Identity Chain", () => { 33 | AS.assertChain(I.Chain, { 34 | a: 1, 35 | ta: I.of(1), 36 | fai: AS.add, 37 | fij: AS.multiply, 38 | tfai: I.of(AS.add), 39 | tfij: I.of(AS.multiply), 40 | fati: (n: number) => I.of(n), 41 | fitj: (n: number) => I.of(n), 42 | }); 43 | }); 44 | 45 | Deno.test("Identity Monad", () => { 46 | AS.assertMonad(I.Monad, { 47 | a: 1, 48 | ta: I.of(1), 49 | fai: AS.add, 50 | fij: AS.multiply, 51 | tfai: I.of(AS.add), 52 | tfij: I.of(AS.multiply), 53 | fati: (n: number) => I.of(n), 54 | fitj: (n: number) => I.of(n), 55 | }); 56 | }); 57 | 58 | Deno.test("Identity of", () => { 59 | assertEquals(I.of(1), 1); 60 | }); 61 | 62 | Deno.test("Identity ap", () => { 63 | const ap = I.ap(I.of(AS.add)); 64 | assertEquals(ap(I.of(1)), I.of(2)); 65 | }); 66 | 67 | Deno.test("Identity map", () => { 68 | const map = I.map(AS.add); 69 | assertEquals(map(I.of(1)), I.of(2)); 70 | }); 71 | 72 | Deno.test("Identity join", () => { 73 | assertEquals(I.join(I.of(I.of(1))), I.of(1)); 74 | }); 75 | 76 | Deno.test("Identity chain", () => { 77 | const chain = I.chain((n: number) => I.of(n + 1)); 78 | assertEquals(chain(I.of(1)), I.of(2)); 79 | }); 80 | -------------------------------------------------------------------------------- /testing/io.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; 2 | 3 | import * as AS from "./assert.ts"; 4 | 5 | import * as I from "../io.ts"; 6 | import * as O from "../option.ts"; 7 | import { semigroupSum } from "../semigroup.ts"; 8 | import { monoidSum } from "../monoid.ts"; 9 | import { pipe } from "../fns.ts"; 10 | 11 | // deno-lint-ignore no-explicit-any 12 | const assertEqualsIO = (a: I.IO, b: I.IO) => assertEquals(a(), b()); 13 | 14 | Deno.test("IO getSemigroup", () => { 15 | const Semigroup = I.getSemigroup(semigroupSum); 16 | const concat = Semigroup.concat(I.of(1)); 17 | 18 | assertEqualsIO(concat(I.of(1)), I.of(2)); 19 | }); 20 | 21 | Deno.test("IO getMonoid", () => { 22 | const Monoid = I.getMonoid(monoidSum); 23 | const empty = Monoid.empty(); 24 | 25 | assertEqualsIO(empty, I.of(0)); 26 | }); 27 | 28 | Deno.test("IO of", () => { 29 | assertEqualsIO(I.of(1), I.of(1)); 30 | }); 31 | 32 | Deno.test("IO ap", () => { 33 | const ap = I.ap(I.of(AS.add)); 34 | assertEqualsIO(ap(I.of(1)), I.of(2)); 35 | }); 36 | 37 | Deno.test("IO map", () => { 38 | const map = I.map(AS.add); 39 | assertEqualsIO(map(I.of(1)), I.of(2)); 40 | }); 41 | 42 | Deno.test("IO join", () => { 43 | assertEqualsIO(I.join(I.of(I.of(1))), I.of(1)); 44 | }); 45 | 46 | Deno.test("IO chain", () => { 47 | const chain = I.chain((n: number) => I.of(n + 1)); 48 | assertEqualsIO(chain(I.of(1)), I.of(2)); 49 | }); 50 | 51 | Deno.test("IO reduce", () => { 52 | const reduce = I.reduce((acc: number, cur: number) => acc + cur, 0); 53 | assertEquals(reduce(I.of(1)), 1); 54 | }); 55 | 56 | Deno.test("IO traverse", () => { 57 | const fold = O.fold(() => -1, (n: I.IO) => n()); 58 | const t0 = I.traverse(O.Applicative); 59 | const t1 = t0((n: number) => n === 0 ? O.none : O.some(n)); 60 | const t2 = fold(t1(I.of(0))); 61 | const t3 = fold(t1(I.of(1))); 62 | 63 | assertEquals(t2, -1); 64 | assertEquals(t3, 1); 65 | }); 66 | 67 | Deno.test("IO extend", () => { 68 | const extend = I.extend((ta: I.IO) => ta() + 1); 69 | assertEqualsIO(extend(I.of(1)), I.of(2)); 70 | }); 71 | 72 | Deno.test("IO sequenceTuple", () => { 73 | const r1 = I.sequenceTuple(I.of(1), I.of("Hello World")); 74 | assertEqualsIO(r1, I.of([1, "Hello World"])); 75 | }); 76 | 77 | Deno.test("IO sequenceStruct", () => { 78 | const r1 = I.sequenceStruct({ a: I.of(1), b: I.of("Hello World") }); 79 | assertEqualsIO(r1, I.of({ a: 1, b: "Hello World" })); 80 | }); 81 | 82 | Deno.test("IO Do, bind, bindTo", () => { 83 | assertEqualsIO( 84 | pipe( 85 | I.Do(), 86 | I.bind("one", () => I.of(1)), 87 | I.bind("two", ({ one }) => I.of(one + one)), 88 | I.map(({ one, two }) => one + two), 89 | ), 90 | I.of(3), 91 | ); 92 | assertEqualsIO( 93 | pipe( 94 | I.of(1), 95 | I.bindTo("one"), 96 | ), 97 | I.of({ one: 1 }), 98 | ); 99 | }); 100 | -------------------------------------------------------------------------------- /testing/io_either.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; 2 | 3 | import * as AS from "./assert.ts"; 4 | 5 | import * as I from "../io_either.ts"; 6 | import * as O from "../option.ts"; 7 | import * as E from "../either.ts"; 8 | import { semigroupSum } from "../semigroup.ts"; 9 | import { monoidSum } from "../monoid.ts"; 10 | import { _, constant, pipe } from "../fns.ts"; 11 | 12 | // deno-lint-ignore no-explicit-any 13 | const assertEqualsIO = (a: I.IOEither, b: I.IOEither) => 14 | assertEquals(a(), b()); 15 | 16 | Deno.test("IOEither left", () => { 17 | assertEqualsIO(I.left(1), I.left(1)); 18 | }); 19 | 20 | Deno.test("IOEither right", () => { 21 | assertEqualsIO(I.right(1), I.right(1)); 22 | }); 23 | 24 | Deno.test("IOEither tryCatch", () => { 25 | assertEqualsIO(I.tryCatch(_, () => 0), I.left(0)); 26 | assertEqualsIO(I.tryCatch(constant(1), () => 0), I.right(1)); 27 | }); 28 | 29 | Deno.test("IOEither fromEither", () => { 30 | assertEqualsIO(I.fromEither(E.right(1)), I.right(1)); 31 | assertEqualsIO(I.fromEither(E.left(1)), I.left(1)); 32 | }); 33 | 34 | Deno.test("IOEither of", () => { 35 | assertEqualsIO(I.of(1), I.of(1)); 36 | }); 37 | 38 | Deno.test("IOEither ap", () => { 39 | const fab = (n: number) => n + 1; 40 | const ap0 = I.ap(I.right(fab)); 41 | const ap1 = I.ap(I.left(0)); 42 | 43 | assertEqualsIO(ap0(I.left(0)), I.left(0)); 44 | assertEqualsIO(ap0(I.right(0)), I.right(1)); 45 | assertEqualsIO(ap1(I.left(1)), I.left(1)); 46 | assertEqualsIO(ap1(I.right(0)), I.left(0)); 47 | }); 48 | 49 | Deno.test("IOEither map", () => { 50 | const fab = (n: number) => n + 1; 51 | const map = I.map(fab); 52 | 53 | assertEqualsIO(map(I.left(0)), I.left(0)); 54 | assertEqualsIO(map(I.right(0)), I.right(1)); 55 | }); 56 | 57 | Deno.test("IOEither join", () => { 58 | const rs0 = I.right(I.right(0)); 59 | const rs1 = I.right(I.left(0)); 60 | const rs2 = I.left(-1); 61 | 62 | assertEqualsIO(I.join(rs0), I.right(0)); 63 | assertEqualsIO(I.join(rs1), I.left(0)); 64 | assertEqualsIO(I.join(rs2), I.left(-1)); 65 | }); 66 | 67 | Deno.test("IOEither chain", () => { 68 | const chain = I.chain((n: number) => n === 0 ? I.left(0) : I.right(n)); 69 | 70 | assertEqualsIO(chain(I.right(0)), I.left(0)); 71 | assertEqualsIO(chain(I.right(1)), I.right(1)); 72 | assertEqualsIO(chain(I.left(0)), I.left(0)); 73 | }); 74 | 75 | Deno.test("IOEither throwError", () => { 76 | assertEqualsIO(I.throwError(0), I.left(0)); 77 | }); 78 | 79 | Deno.test("IOEither bimap", () => { 80 | const bimap = I.bimap((n: number) => n + 1, (n: number) => n + 1); 81 | 82 | assertEqualsIO(bimap(I.left(0)), I.left(1)); 83 | assertEqualsIO(bimap(I.right(0)), I.right(1)); 84 | }); 85 | 86 | Deno.test("IOEither mapLeft", () => { 87 | const mapLeft = I.mapLeft((n: number) => n + 1); 88 | 89 | assertEqualsIO(mapLeft(I.left(0)), I.left(1)); 90 | assertEqualsIO(mapLeft(I.right(0)), I.right(0)); 91 | }); 92 | 93 | Deno.test("IOEither reduce", () => { 94 | const reduce = I.reduce((a: number, c: number) => a + c, 0); 95 | 96 | assertEquals(reduce(I.left(-1)), 0); 97 | assertEquals(reduce(I.right(1)), 1); 98 | }); 99 | 100 | Deno.test("IOEither extend", () => { 101 | const extend = I.extend((ta: I.IOEither) => 102 | pipe(ta(), E.fold((n) => n, (n) => n + 1)) 103 | ); 104 | 105 | assertEqualsIO(extend(I.left(0)), I.right(0)); 106 | assertEqualsIO(extend(I.right(0)), I.right(1)); 107 | }); 108 | 109 | Deno.test("IOEither alt", () => { 110 | assertEqualsIO(pipe(I.right(0), I.alt(I.right(1))), I.right(0)); 111 | assertEqualsIO(pipe(I.right(0), I.alt(I.left(1))), I.right(0)); 112 | assertEqualsIO(pipe(I.left(0), I.alt(I.right(1))), I.right(1)); 113 | assertEqualsIO(pipe(I.left(0), I.alt(I.left(1))), I.left(1)); 114 | }); 115 | 116 | Deno.test("IOEither chainLeft", () => { 117 | const chainLeft = I.chainLeft((n: number) => 118 | n === 0 ? I.left(n + 1) : I.right(n + 1) 119 | ); 120 | 121 | assertEqualsIO(chainLeft(I.right(0)), I.right(0)); 122 | assertEqualsIO(chainLeft(I.right(1)), I.right(1)); 123 | assertEqualsIO(chainLeft(I.left(0)), I.left(1)); 124 | assertEqualsIO(chainLeft(I.left(1)), I.right(2)); 125 | }); 126 | 127 | Deno.test("IOEither sequenceTuple", () => { 128 | assertEqualsIO(I.sequenceTuple(I.right(0), I.right(0)), I.right([0, 0])); 129 | assertEqualsIO(I.sequenceTuple(I.right(0), I.left(0)), I.left(0)); 130 | assertEqualsIO(I.sequenceTuple(I.left(0), I.right(0)), I.left(0)); 131 | assertEqualsIO(I.sequenceTuple(I.left(0), I.left(1)), I.left(1)); 132 | }); 133 | 134 | Deno.test("IOEither sequenceStruct", () => { 135 | assertEqualsIO( 136 | I.sequenceStruct({ a: I.right(0), b: I.right(0) }), 137 | I.right({ a: 0, b: 0 }), 138 | ); 139 | assertEqualsIO(I.sequenceStruct({ a: I.right(0), b: I.left(0) }), I.left(0)); 140 | assertEqualsIO(I.sequenceStruct({ a: I.left(0), b: I.right(0) }), I.left(0)); 141 | assertEqualsIO(I.sequenceStruct({ a: I.left(0), b: I.left(1) }), I.left(1)); 142 | }); 143 | 144 | Deno.test("Datum Do, bind, bindTo", () => { 145 | assertEqualsIO( 146 | pipe( 147 | I.Do(), 148 | I.bind("one", () => I.right(1)), 149 | I.bind("two", ({ one }) => I.right(one + one)), 150 | I.map(({ one, two }) => one + two), 151 | ), 152 | I.right(3), 153 | ); 154 | assertEqualsIO( 155 | pipe( 156 | I.right(1), 157 | I.bindTo("one"), 158 | ), 159 | I.right({ one: 1 }), 160 | ); 161 | }); 162 | 163 | Deno.test("Either widen", () => { 164 | assertEqualsIO(pipe(I.right(1), I.widen()), I.right(1)); 165 | }); 166 | -------------------------------------------------------------------------------- /testing/map.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; 2 | 3 | import * as AS from "./assert.ts"; 4 | 5 | import * as M from "../map.ts"; 6 | import * as O from "../option.ts"; 7 | import { setoidNumber } from "../setoid.ts"; 8 | import { monoidSum } from "../monoid.ts"; 9 | import { ordNumber } from "../ord.ts"; 10 | import { semigroupSum } from "../semigroup.ts"; 11 | import { pipe } from "../fns.ts"; 12 | 13 | Deno.test("Map zero", () => { 14 | assertEquals(M.zero, new Map()); 15 | }); 16 | 17 | Deno.test("Map empty", () => { 18 | assertEquals(M.empty(), new Map()); 19 | }); 20 | 21 | Deno.test("Map singleton", () => { 22 | assertEquals(M.singleton(1, 1), new Map([[1, 1]])); 23 | }); 24 | 25 | Deno.test("Map Functor", () => { 26 | AS.assertFunctor(M.Functor, { 27 | ta: M.singleton(1, 1) as Map, 28 | fai: AS.add, 29 | fij: AS.multiply, 30 | }); 31 | }); 32 | 33 | Deno.test("Map Bifunctor", () => { 34 | AS.assertBifunctor(M.Bifunctor, { 35 | tab: M.singleton(1, 1), 36 | fai: AS.add, 37 | fij: AS.multiply, 38 | fbx: AS.add, 39 | fxy: AS.multiply, 40 | }); 41 | }); 42 | 43 | Deno.test("Map getShow", () => { 44 | const showNumber = { show: (n: number) => n.toString() }; 45 | const Show = M.getShow(showNumber, showNumber); 46 | 47 | assertEquals(Show.show(M.singleton(1, 1)), "new Map([[1, 1]])"); 48 | }); 49 | 50 | Deno.test("Map getSetoid", () => { 51 | const Setoid = M.getSetoid(setoidNumber, setoidNumber); 52 | 53 | AS.assertSetoid(Setoid, { 54 | a: M.singleton(1, 1), 55 | b: M.singleton(1, 1), 56 | c: M.singleton(1, 1), 57 | z: M.singleton(2, 2), 58 | }); 59 | }); 60 | 61 | Deno.test("Map getMonoid", () => { 62 | const Monoid = M.getMonoid(setoidNumber, semigroupSum); 63 | 64 | AS.assertMonoid(Monoid, { 65 | a: M.singleton(1, 1), 66 | b: M.singleton(2, 2), 67 | c: M.singleton(3, 3), 68 | }); 69 | }); 70 | 71 | Deno.test("Map size", () => { 72 | assertEquals(M.size(M.zero), 0); 73 | assertEquals(M.size(M.singleton(1, 1)), 1); 74 | }); 75 | 76 | Deno.test("Map isEmpty", () => { 77 | assertEquals(M.isEmpty(M.empty()), true); 78 | assertEquals(M.isEmpty(M.singleton(1, 1)), false); 79 | }); 80 | 81 | Deno.test("Map lookupWithKey", () => { 82 | const lookupWithKey = M.lookupWithKey(setoidNumber); 83 | 84 | assertEquals(pipe(M.singleton(1, 1), lookupWithKey(1)), O.some([1, 1])); 85 | assertEquals(pipe(M.singleton(2, 2), lookupWithKey(1)), O.none); 86 | }); 87 | 88 | Deno.test("Map lookup", () => { 89 | const lookup = M.lookup(setoidNumber); 90 | 91 | assertEquals(pipe(M.singleton(1, 1), lookup(1)), O.some(1)); 92 | assertEquals(pipe(M.singleton(1, 1), lookup(2)), O.none); 93 | }); 94 | 95 | Deno.test("Map member", () => { 96 | const member = M.member(setoidNumber); 97 | 98 | assertEquals(pipe(M.singleton(1, 1), member(1)), true); 99 | assertEquals(pipe(M.singleton(1, 1), member(2)), false); 100 | }); 101 | 102 | Deno.test("Map elem", () => { 103 | const elem = M.elem(setoidNumber); 104 | 105 | assertEquals(pipe(M.singleton(1, 1), elem(1)), true); 106 | assertEquals(pipe(M.singleton(1, 1), elem(2)), false); 107 | }); 108 | 109 | Deno.test("Map keys", () => { 110 | const keys = M.keys(ordNumber); 111 | 112 | const ta = new Map([[1, 1], [2, 2], [3, 3]]); 113 | const tb = M.empty(); 114 | 115 | assertEquals(pipe(ta, keys), [1, 2, 3]); 116 | assertEquals(pipe(tb, keys), []); 117 | }); 118 | 119 | Deno.test("Map values", () => { 120 | const values = M.values(ordNumber); 121 | 122 | const ta = new Map([[1, 1], [2, 2], [3, 3]]); 123 | const tb = M.empty(); 124 | 125 | assertEquals(pipe(ta, values), [1, 2, 3]); 126 | assertEquals(pipe(tb, values), []); 127 | }); 128 | 129 | Deno.test("Map collect", () => { 130 | const collect = M.collect(ordNumber); 131 | const c = collect((k: number, v: number) => k + v); 132 | 133 | const ta = new Map([[1, 1], [2, 2], [3, 3]]); 134 | const tb = M.empty(); 135 | 136 | assertEquals(pipe(ta, c), [2, 4, 6]); 137 | assertEquals(pipe(tb, c), []); 138 | }); 139 | 140 | Deno.test("Map insertAt", () => { 141 | const insertAt = M.insertAt(setoidNumber); 142 | 143 | assertEquals(pipe(M.singleton(1, 1), insertAt(1, 2)), M.singleton(1, 2)); 144 | assertEquals( 145 | pipe(M.empty(), insertAt(1, 1)), 146 | M.singleton(1, 1), 147 | ); 148 | }); 149 | 150 | Deno.test("Map deleteAt", () => { 151 | const deleteAt = M.deleteAt(setoidNumber); 152 | 153 | assertEquals(pipe(M.empty(), deleteAt(1)), M.zero); 154 | assertEquals(pipe(M.singleton(1, 1), deleteAt(1)), M.zero); 155 | }); 156 | 157 | Deno.test("Map updateAt", () => { 158 | const updateAt = M.updateAt(setoidNumber); 159 | 160 | assertEquals( 161 | pipe(M.singleton(1, 1), updateAt(1, 2)), 162 | O.some(M.singleton(1, 2)), 163 | ); 164 | assertEquals(pipe(M.singleton(1, 1), updateAt(2, 2)), O.none); 165 | }); 166 | 167 | Deno.test("Map modifyAt", () => { 168 | const modifyAt = M.modifyAt(setoidNumber); 169 | 170 | assertEquals( 171 | pipe(M.singleton(1, 1), modifyAt(1, (n) => n + 1)), 172 | O.some(M.singleton(1, 2)), 173 | ); 174 | assertEquals(pipe(M.singleton(1, 1), modifyAt(2, (n) => n + 1)), O.none); 175 | }); 176 | 177 | Deno.test("Map pop", () => { 178 | const pop = M.pop(setoidNumber); 179 | 180 | assertEquals(pipe(M.singleton(1, 1), pop(1)), O.some([1, M.empty()])); 181 | assertEquals(pipe(M.singleton(1, 1), pop(2)), O.none); 182 | }); 183 | 184 | Deno.test("Map isSubmap", () => { 185 | const isSubmap = M.isSubmap(setoidNumber, setoidNumber); 186 | 187 | assertEquals(pipe(M.singleton(1, 1), isSubmap(M.empty())), true); 188 | assertEquals(pipe(M.singleton(1, 1), isSubmap(M.singleton(2, 2))), false); 189 | }); 190 | -------------------------------------------------------------------------------- /testing/monoid.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; 2 | 3 | import * as AS from "./assert.ts"; 4 | 5 | import * as M from "../monoid.ts"; 6 | import * as O from "../option.ts"; 7 | 8 | Deno.test("Monoid monoidAll", () => { 9 | AS.assertMonoid(M.monoidAll, { a: true, b: true, c: true }); 10 | }); 11 | 12 | Deno.test("Monoid monoidAny", () => { 13 | AS.assertMonoid(M.monoidAny, { a: true, b: true, c: true }); 14 | }); 15 | 16 | Deno.test("Monoid monoidSum", () => { 17 | AS.assertMonoid(M.monoidSum, { a: 1, b: 1, c: 1 }); 18 | }); 19 | 20 | Deno.test("Monoid monoidProduct", () => { 21 | AS.assertMonoid(M.monoidProduct, { a: 2, b: 1, c: 1 }); 22 | }); 23 | 24 | Deno.test("Monoid monoidString", () => { 25 | AS.assertMonoid(M.monoidString, { a: "a", b: "b", c: "c" }); 26 | }); 27 | 28 | Deno.test("Monoid monoidVoid", () => { 29 | AS.assertMonoid(M.monoidVoid, { a: undefined, b: undefined, c: undefined }); 30 | }); 31 | 32 | Deno.test("Monoid getTupleMonoid", () => { 33 | const Semigroup = M.getTupleMonoid(M.monoidSum, M.monoidString); 34 | 35 | AS.assertSemigroup(Semigroup, { a: [1, "a"], b: [2, "b"], c: [3, "c"] }); 36 | 37 | assertEquals(Semigroup.empty(), [0, ""]); 38 | }); 39 | 40 | Deno.test("Monoid getDualMonoid", () => { 41 | const Monoid = M.getDualMonoid(M.monoidSum); 42 | 43 | AS.assertMonoid(Monoid, { a: 1, b: 2, c: 3 }); 44 | }); 45 | 46 | Deno.test("Monoid getStructMonoid", () => { 47 | const Monoid = M.getStructMonoid({ a: M.monoidSum, b: M.monoidString }); 48 | 49 | AS.assertMonoid(Monoid, { 50 | a: { a: 1, b: "1" }, 51 | b: { a: 2, b: "2" }, 52 | c: { a: 3, b: "3" }, 53 | }); 54 | }); 55 | 56 | Deno.test("Monoid fold", () => { 57 | const fold = M.fold(M.monoidSum); 58 | 59 | assertEquals(fold([]), M.monoidSum.empty()); 60 | assertEquals(fold([1, 2, 3]), 6); 61 | }); 62 | -------------------------------------------------------------------------------- /testing/optics/at.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; 2 | 3 | import * as A from "../../optics/at.ts"; 4 | import * as O from "../../option.ts"; 5 | 6 | Deno.test("At atRecord", () => { 7 | const at = A.atRecord(); 8 | const { get, set } = at.at("one"); 9 | 10 | assertEquals(get({}), O.none); 11 | assertEquals(get({ two: 1 }), O.none); 12 | assertEquals(get({ one: 1 }), O.some(1)); 13 | 14 | assertEquals(set(O.none)({}), {}); 15 | assertEquals(set(O.none)({ one: 1 }), {}); 16 | assertEquals(set(O.some(1))({}), { one: 1 }); 17 | assertEquals(set(O.some(1))({ one: 2 }), { one: 1 }); 18 | assertEquals(set(O.some(1))({ one: 1 }), { one: 1 }); 19 | }); 20 | 21 | Deno.test("At atMap", () => { 22 | const at = A.atMap(); 23 | const { get, set } = at.at("one"); 24 | 25 | assertEquals(get(new Map()), O.none); 26 | assertEquals(get(new Map([["one", 1]])), O.some(1)); 27 | assertEquals(get(new Map([["two", 1]])), O.none); 28 | 29 | assertEquals(set(O.none)(new Map()), new Map()); 30 | assertEquals(set(O.none)(new Map([["one", 1]])), new Map()); 31 | assertEquals(set(O.some(1))(new Map()), new Map([["one", 1]])); 32 | assertEquals(set(O.some(1))(new Map([["one", 2]])), new Map([["one", 1]])); 33 | assertEquals(set(O.some(1))(new Map([["one", 1]])), new Map([["one", 1]])); 34 | }); 35 | -------------------------------------------------------------------------------- /testing/optics/from_traversable.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; 2 | 3 | import * as F from "../../optics/from_traversable.ts"; 4 | import * as O from "../../option.ts"; 5 | 6 | Deno.test("FromTraversable fromTraversable", () => { 7 | const createTraversal = F.fromTraversable(O.Traversable); 8 | const traversal = createTraversal(); 9 | 10 | assertEquals(O.Traversable, traversal); 11 | }); 12 | -------------------------------------------------------------------------------- /testing/optics/index.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; 2 | 3 | import * as I from "../../optics/index.ts"; 4 | import * as O from "../../option.ts"; 5 | 6 | Deno.test("Index indexArray", () => { 7 | const index = I.indexArray(); 8 | const { getOption, set } = index.index(1); 9 | 10 | assertEquals(getOption([]), O.none); 11 | assertEquals(getOption([1]), O.none); 12 | assertEquals(getOption([1, 2]), O.some(2)); 13 | 14 | assertEquals(set(1)([]), []); 15 | assertEquals(set(1)([1]), [1]); 16 | assertEquals(set(1)([1, 2]), [1, 1]); 17 | }); 18 | 19 | Deno.test("Index indexRecord", () => { 20 | const index = I.indexRecord(); 21 | const { getOption, set } = index.index("one"); 22 | 23 | assertEquals(getOption({}), O.none); 24 | assertEquals(getOption({ two: 2 }), O.none); 25 | assertEquals(getOption({ one: 1 }), O.some(1)); 26 | 27 | assertEquals(set(2)({}), {}); 28 | assertEquals(set(2)({ two: 2 }), { two: 2 }); 29 | assertEquals(set(2)({ one: 1 }), { one: 2 }); 30 | assertEquals(set(1)({ one: 1 }), { one: 1 }); 31 | }); 32 | -------------------------------------------------------------------------------- /testing/optics/iso.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; 2 | 3 | import * as I from "../../optics/iso.ts"; 4 | import * as O from "../../option.ts"; 5 | import * as E from "../../either.ts"; 6 | import { pipe } from "../../fns.ts"; 7 | 8 | const iso = I.make( 9 | (n: number) => n.toString(), 10 | (s: string) => parseFloat(s), 11 | ); 12 | const i0 = I.make( 13 | (n: number) => n === 0 ? O.none : O.some(n), 14 | O.fold(() => 0, (n) => n), 15 | ); 16 | const i1 = I.make( 17 | (n: number): E.Either => n === 0 ? E.left(n) : E.right(n), 18 | E.fold((n) => n, (n) => n), 19 | ); 20 | 21 | Deno.test("Iso make", () => { 22 | const { get, reverseGet } = iso; 23 | 24 | const nums = [-1, 0, 1, 1.1, 0.0000000900]; 25 | 26 | nums.forEach((n) => { 27 | assertEquals(reverseGet(get(n)), n); 28 | }); 29 | }); 30 | 31 | Deno.test("Iso asLens", () => { 32 | const { set } = I.asLens(iso); 33 | 34 | assertEquals(set("10")(1), 10); 35 | }); 36 | 37 | Deno.test("Iso asPrism", () => { 38 | const { getOption } = I.asPrism(iso); 39 | 40 | assertEquals(getOption(1), O.some("1")); 41 | }); 42 | 43 | Deno.test("Iso asOptional", () => { 44 | const { getOption, set } = I.asOptional(iso); 45 | 46 | assertEquals(getOption(1), O.some("1")); 47 | assertEquals(set("1")(2), 1); 48 | }); 49 | 50 | Deno.test("Iso asTraversal", () => { 51 | const { traverse } = I.asTraversal(iso); 52 | const t0 = traverse(O.Applicative); 53 | const t1 = t0((s) => s === "0" ? O.none : O.some(s)); 54 | 55 | assertEquals(t1(0), O.none); 56 | assertEquals(t1(1), O.some(1)); 57 | }); 58 | 59 | Deno.test("Iso id", () => { 60 | const { get, reverseGet } = I.id(); 61 | 62 | assertEquals(get(1), 1); 63 | assertEquals(reverseGet(1), 1); 64 | }); 65 | 66 | Deno.test("Iso compose", () => { 67 | const { get, reverseGet } = pipe(I.id(), I.compose(iso)); 68 | 69 | const nums = [-1, 0, 1, 1.1, 0.0000000900]; 70 | nums.forEach((n) => { 71 | assertEquals(reverseGet(get(n)), n); 72 | }); 73 | }); 74 | 75 | Deno.test("Iso composeLens", () => { 76 | const { get, set } = pipe(I.id(), I.composeLens(I.asLens(iso))); 77 | 78 | assertEquals(get(1), "1"); 79 | assertEquals(set("2")(1), 2); 80 | }); 81 | 82 | Deno.test("Iso composePrism", () => { 83 | const { getOption, reverseGet } = pipe( 84 | I.id(), 85 | I.composePrism(I.asPrism(iso)), 86 | ); 87 | 88 | assertEquals(getOption(1), O.some("1")); 89 | assertEquals(reverseGet("1"), 1); 90 | }); 91 | 92 | Deno.test("Iso composeOptional", () => { 93 | const { getOption, set } = pipe( 94 | I.id(), 95 | I.composeOptional(I.asOptional(iso)), 96 | ); 97 | 98 | assertEquals(getOption(1), O.some("1")); 99 | assertEquals(set("2")(1), 2); 100 | }); 101 | 102 | Deno.test("Iso composeTraversal", () => { 103 | const { traverse } = pipe( 104 | I.id(), 105 | I.composeTraversal(I.asTraversal(iso)), 106 | ); 107 | const t0 = traverse(O.Applicative); 108 | const t1 = t0(O.some); 109 | 110 | assertEquals(t1(0), O.some(0)); 111 | }); 112 | 113 | Deno.test("Iso modify", () => { 114 | const modify = I.modify((n: string) => n.includes(".") ? n : `${n}.01`); 115 | const m0 = modify(iso); 116 | 117 | assertEquals(m0(0), 0.01); 118 | assertEquals(m0(0.1), 0.1); 119 | }); 120 | 121 | Deno.test("Iso map", () => { 122 | const map = I.map((n: number) => n + 1, (n: number) => n - 1); 123 | const { get, reverseGet } = map(I.id()); 124 | 125 | assertEquals(get(1), 2); 126 | assertEquals(reverseGet(2), 1); 127 | }); 128 | 129 | Deno.test("Iso reverse", () => { 130 | const { get, reverseGet } = I.reverse(iso); 131 | 132 | assertEquals(get("1"), 1); 133 | assertEquals(reverseGet(1), "1"); 134 | }); 135 | 136 | Deno.test("Iso traverse", () => { 137 | const { traverse } = pipe(i0, I.traverse(O.Traversable)); 138 | const t0 = traverse(O.Applicative); 139 | const t1 = t0((n) => n === 0 ? O.none : O.some(n)); 140 | 141 | assertEquals(t1(0), O.some(0)); 142 | assertEquals(t1(1), O.some(1)); 143 | }); 144 | 145 | Deno.test("Iso some", () => { 146 | const { getOption, reverseGet } = I.some(i0); 147 | 148 | assertEquals(getOption(0), O.none); 149 | assertEquals(getOption(1), O.some(1)); 150 | assertEquals(reverseGet(0), 0); 151 | assertEquals(reverseGet(1), 1); 152 | }); 153 | 154 | Deno.test("Iso right", () => { 155 | const { getOption, reverseGet } = I.right(i1); 156 | 157 | assertEquals(getOption(0), O.none); 158 | assertEquals(getOption(1), O.some(1)); 159 | assertEquals(reverseGet(0), 0); 160 | assertEquals(reverseGet(1), 1); 161 | }); 162 | 163 | Deno.test("Iso left", () => { 164 | const { getOption, reverseGet } = I.left(i1); 165 | 166 | assertEquals(getOption(0), O.some(0)); 167 | assertEquals(getOption(1), O.none); 168 | assertEquals(reverseGet(0), 0); 169 | assertEquals(reverseGet(1), 1); 170 | }); 171 | -------------------------------------------------------------------------------- /testing/optics/traversal.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; 2 | 3 | import * as T from "../../optics/traversal.ts"; 4 | import * as I from "../../optics/iso.ts"; 5 | import * as L from "../../optics/lens.ts"; 6 | import * as M from "../../optics/optional.ts"; 7 | import * as P from "../../optics/prism.ts"; 8 | import * as O from "../../option.ts"; 9 | import * as E from "../../either.ts"; 10 | import * as A from "../../array.ts"; 11 | import { monoidSum } from "../../monoid.ts"; 12 | import { pipe } from "../../fns.ts"; 13 | 14 | const positive = O.fromPredicate((n: number) => n > 0); 15 | 16 | const iso = I.make((n: number) => n.toString(), (s: string) => parseFloat(s)); 17 | 18 | type T1 = { one: number }; 19 | 20 | type T2 = { one: number; two: number; three: number }; 21 | 22 | Deno.test("Traversal compose", () => { 23 | const getAll = pipe(T.id(), T.compose(I.asTraversal(iso)), T.getAll); 24 | assertEquals(getAll(0), ["0"]); 25 | }); 26 | 27 | Deno.test("Traversal composeIso", () => { 28 | const getAll = pipe(T.id(), T.composeIso(iso), T.getAll); 29 | assertEquals(getAll(1), ["1"]); 30 | }); 31 | 32 | Deno.test("Traversal composeLens", () => { 33 | const lens = L.make( 34 | (n: number) => n.toString(), 35 | (a: string) => (_) => parseFloat(a), 36 | ); 37 | const getAll = pipe(T.id(), T.composeLens(lens), T.getAll); 38 | assertEquals(getAll(0), ["0"]); 39 | }); 40 | 41 | Deno.test("Traversal composePrism", () => { 42 | const getAll = pipe( 43 | T.id>(), 44 | T.composePrism(P.some()), 45 | T.getAll, 46 | ); 47 | assertEquals(getAll(O.none), []); 48 | assertEquals(getAll(O.some(1)), [1]); 49 | }); 50 | 51 | Deno.test("Traversal composeOptional", () => { 52 | const optional = M.id(); 53 | const getAll = pipe(T.id(), T.composeOptional(optional), T.getAll); 54 | assertEquals(getAll(0), [0]); 55 | }); 56 | 57 | Deno.test("Traversal id", () => { 58 | const getAll = pipe(T.id(), T.getAll); 59 | assertEquals(getAll(0), [0]); 60 | }); 61 | 62 | Deno.test("Traversal modify", () => { 63 | const modify = pipe(T.id(), T.modify((n) => n + 1)); 64 | assertEquals(modify(1), 2); 65 | }); 66 | 67 | Deno.test("Traversal filter", () => { 68 | const getAll = pipe( 69 | T.fromTraversable(A.Traversable)(), 70 | T.filter((n) => n > 0), 71 | T.getAll, 72 | ); 73 | assertEquals(getAll([]), []); 74 | assertEquals(getAll([0]), []); 75 | assertEquals(getAll([0, 2, 3]), [2, 3]); 76 | assertEquals(getAll([1, 2, 3]), [1, 2, 3]); 77 | }); 78 | 79 | Deno.test("Traversal set", () => { 80 | const set = pipe(T.id(), T.set(1)); 81 | assertEquals(set(0), 1); 82 | }); 83 | 84 | Deno.test("Traversal prop", () => { 85 | const getAll = pipe(T.id(), T.prop("one"), T.getAll); 86 | assertEquals(getAll({ one: 1 }), [1]); 87 | }); 88 | 89 | Deno.test("Traversal props", () => { 90 | const getAll = pipe(T.id(), T.props("one", "two"), T.getAll); 91 | assertEquals(getAll({ one: 1, two: 2, three: 3 }), [{ one: 1, two: 2 }]); 92 | }); 93 | 94 | Deno.test("Traversal index", () => { 95 | const getAll = pipe(T.id>(), T.index(0), T.getAll); 96 | assertEquals(getAll([]), []); 97 | assertEquals(getAll([1]), [1]); 98 | assertEquals(getAll([1, 2]), [1]); 99 | }); 100 | 101 | Deno.test("Traversal key", () => { 102 | const getAll = pipe( 103 | T.id>>(), 104 | T.key("one"), 105 | T.getAll, 106 | ); 107 | assertEquals(getAll({}), []); 108 | assertEquals(getAll({ one: 1 }), [1]); 109 | assertEquals(getAll({ one: 1, two: 2 }), [1]); 110 | assertEquals(getAll({ two: 2 }), []); 111 | }); 112 | 113 | Deno.test("Traversal atKey", () => { 114 | const getAll = pipe( 115 | T.id>>(), 116 | T.atKey("one"), 117 | T.getAll, 118 | ); 119 | assertEquals(getAll({}), [O.none]); 120 | assertEquals(getAll({ one: 1 }), [O.some(1)]); 121 | }); 122 | 123 | Deno.test("Traversal traverse", () => { 124 | const getAll = pipe( 125 | T.id>(), 126 | T.traverse(A.Traversable), 127 | T.getAll, 128 | ); 129 | assertEquals(getAll([1, 2, 3]), [1, 2, 3]); 130 | assertEquals(getAll([]), []); 131 | }); 132 | 133 | Deno.test("Traversal foldMap", () => { 134 | const foldMapSum = T.foldMap(monoidSum); 135 | const traverseNumberArray = pipe(T.fromTraversable(A.Traversable)()); 136 | const foldMap = pipe(traverseNumberArray, foldMapSum((n) => n)); 137 | assertEquals(foldMap([]), 0); 138 | assertEquals(foldMap([1, 2, 3]), 6); 139 | }); 140 | 141 | Deno.test("Traversal getAll", () => { 142 | const getAll = pipe(T.fromTraversable(A.Traversable)(), T.getAll); 143 | assertEquals(getAll([]), []); 144 | assertEquals(getAll([1, 2, 3]), [1, 2, 3]); 145 | }); 146 | 147 | Deno.test("Traversal some", () => { 148 | const getAll = pipe(T.id>(), T.some, T.getAll); 149 | assertEquals(getAll(O.none), []); 150 | assertEquals(getAll(O.some(1)), [1]); 151 | }); 152 | 153 | Deno.test("Traversal right", () => { 154 | const getAll = pipe(T.id>(), T.right, T.getAll); 155 | assertEquals(getAll(E.left(1)), []); 156 | assertEquals(getAll(E.right(1)), [1]); 157 | }); 158 | 159 | Deno.test("Traversal left", () => { 160 | const getAll = pipe(T.id>(), T.left, T.getAll); 161 | assertEquals(getAll(E.right(1)), []); 162 | assertEquals(getAll(E.left(1)), [1]); 163 | }); 164 | -------------------------------------------------------------------------------- /testing/ord.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | assertEquals, 3 | assertExists, 4 | } from "https://deno.land/std/testing/asserts.ts"; 5 | 6 | import * as AS from "./assert.ts"; 7 | 8 | import * as O from "../ord.ts"; 9 | 10 | Deno.test("Ord ordString", () => { 11 | AS.assertOrd(O.ordString, { a: "a", b: "b" }); 12 | }); 13 | 14 | Deno.test("Ord ordNumber", () => { 15 | AS.assertOrd(O.ordNumber, { a: 0, b: 1 }); 16 | }); 17 | 18 | Deno.test("Ord ordBoolean", () => { 19 | AS.assertOrd(O.ordBoolean, { a: true, b: false }); 20 | }); 21 | 22 | Deno.test("Ord compare", () => { 23 | const compare = O.compare(O.ordNumber); 24 | assertEquals(compare(0, 0), 0); 25 | assertEquals(compare(0, 1), -1); 26 | assertEquals(compare(1, 0), 1); 27 | }); 28 | 29 | Deno.test("Ord lt", () => { 30 | const lt = O.lt(O.ordNumber); 31 | assertEquals(lt(0)(0), false); 32 | assertEquals(lt(0)(1), true); 33 | assertEquals(lt(1)(0), false); 34 | }); 35 | 36 | Deno.test("Ord gt", () => { 37 | const gt = O.gt(O.ordNumber); 38 | assertEquals(gt(0)(0), false); 39 | assertEquals(gt(0)(1), false); 40 | assertEquals(gt(1)(0), true); 41 | }); 42 | 43 | Deno.test("Ord lte", () => { 44 | const lte = O.lte(O.ordNumber); 45 | assertEquals(lte(0)(0), true); 46 | assertEquals(lte(0)(1), true); 47 | assertEquals(lte(1)(0), false); 48 | }); 49 | 50 | Deno.test("Ord gte", () => { 51 | const gte = O.gte(O.ordNumber); 52 | assertEquals(gte(0)(0), true); 53 | assertEquals(gte(0)(1), false); 54 | assertEquals(gte(1)(0), true); 55 | }); 56 | 57 | Deno.test("Ord eq", () => { 58 | const eq = O.eq(O.ordNumber); 59 | assertEquals(eq(0)(0), true); 60 | assertEquals(eq(0)(1), false); 61 | assertEquals(eq(1)(0), false); 62 | }); 63 | 64 | Deno.test("Ord min", () => { 65 | const min = O.min(O.ordNumber); 66 | assertEquals(min(0)(0), 0); 67 | assertEquals(min(0)(1), 0); 68 | assertEquals(min(1)(0), 0); 69 | }); 70 | 71 | Deno.test("Ord max", () => { 72 | const max = O.max(O.ordNumber); 73 | assertEquals(max(0)(0), 0); 74 | assertEquals(max(0)(1), 1); 75 | assertEquals(max(1)(0), 1); 76 | }); 77 | 78 | Deno.test("Ord clamp", () => { 79 | const clamp = O.clamp(O.ordNumber); 80 | assertEquals(clamp(0, 2)(-1), 0); 81 | assertEquals(clamp(0, 2)(0), 0); 82 | assertEquals(clamp(0, 2)(1), 1); 83 | assertEquals(clamp(0, 2)(2), 2); 84 | assertEquals(clamp(0, 2)(3), 2); 85 | }); 86 | 87 | Deno.test("Ord between", () => { 88 | const between = O.between(O.ordNumber); 89 | assertEquals(between(0, 2)(-1), false); 90 | assertEquals(between(0, 2)(0), false); 91 | assertEquals(between(0, 2)(1), true); 92 | assertEquals(between(0, 2)(2), false); 93 | assertEquals(between(0, 2)(3), false); 94 | }); 95 | 96 | Deno.test("Ord getOrdUtilities", () => { 97 | assertExists(O.getOrdUtilities(O.ordNumber)); 98 | }); 99 | -------------------------------------------------------------------------------- /testing/reader.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; 2 | 3 | import * as R from "../reader.ts"; 4 | import { pipe } from "../fns.ts"; 5 | 6 | import * as AS from "./assert.ts"; 7 | 8 | // deno-lint-ignore no-explicit-any 9 | const assertEqualsR = (a: R.Reader, b: R.Reader) => 10 | assertEquals(a(0), b(0)); 11 | 12 | Deno.test("Reader ask", () => { 13 | assertEqualsR(R.ask(), R.ask()); 14 | }); 15 | 16 | Deno.test("Reader asks", () => { 17 | assertEqualsR(R.asks(AS.add), R.asks(AS.add)); 18 | }); 19 | 20 | Deno.test("Reader of", () => { 21 | assertEqualsR(R.of(0), R.ask()); 22 | }); 23 | 24 | Deno.test("Reader ap", () => { 25 | assertEqualsR(pipe(R.make(0), R.ap(R.of(AS.add))), R.make(1)); 26 | }); 27 | 28 | Deno.test("Reader map", () => { 29 | assertEqualsR(pipe(R.make(0), R.map(AS.add)), R.make(1)); 30 | }); 31 | 32 | Deno.test("Reader join", () => { 33 | const tta = R.asks((n: number) => R.make(n)); 34 | assertEquals(R.join(tta)(0), 0); 35 | }); 36 | 37 | Deno.test("Reader chain", () => { 38 | const chain = R.chain((n: number) => R.make(n + 1)); 39 | assertEqualsR(chain(R.make(0)), R.make(1)); 40 | }); 41 | 42 | Deno.test("Reader Do, bind, bindTo", () => { 43 | assertEqualsR( 44 | pipe( 45 | R.Do(), 46 | R.bind("one", () => R.make(1)), 47 | R.bind("two", ({ one }) => R.make(one + one)), 48 | R.map(({ one, two }) => one + two), 49 | ), 50 | R.make(3), 51 | ); 52 | assertEqualsR( 53 | pipe( 54 | R.make(1), 55 | R.bindTo("one"), 56 | ), 57 | R.asks((_: number) => ({ one: 1 })), 58 | ); 59 | }); 60 | -------------------------------------------------------------------------------- /testing/reader_either.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; 2 | 3 | import type * as HKT from "../hkt.ts"; 4 | 5 | import * as R from "../reader_either.ts"; 6 | import * as E from "../either.ts"; 7 | import { _, pipe } from "../fns.ts"; 8 | 9 | import * as AS from "./assert.ts"; 10 | 11 | const assertEqualsRE = ( 12 | // deno-lint-ignore no-explicit-any 13 | a: R.ReaderEither, 14 | // deno-lint-ignore no-explicit-any 15 | b: R.ReaderEither, 16 | ) => assertEquals(a(0), b(0)); 17 | 18 | Deno.test("ReaderEither ask", () => { 19 | assertEqualsRE(R.ask(), R.ask()); 20 | }); 21 | 22 | Deno.test("ReaderEither asks", () => { 23 | assertEqualsRE(R.asks((n) => n), R.of(0)); 24 | }); 25 | 26 | Deno.test("ReaderEither left", () => { 27 | assertEquals(R.left("Hello")(0 as never), E.left("Hello")); 28 | }); 29 | 30 | Deno.test("ReaderEither right", () => { 31 | assertEquals(R.right("Hello")(0 as never), E.right("Hello")); 32 | }); 33 | 34 | Deno.test("ReaderEither tryCatch", () => { 35 | assertEqualsRE(R.tryCatch(_, () => -1), R.left(-1)); 36 | assertEqualsRE(R.tryCatch(() => 0, () => -1), R.right(0)); 37 | }); 38 | 39 | Deno.test("ReaderEither fromEither", () => { 40 | assertEqualsRE(R.fromEither(E.left(0)), R.left(0)); 41 | assertEqualsRE(R.fromEither(E.right(0)), R.right(0)); 42 | }); 43 | 44 | Deno.test("ReaderEither of", () => { 45 | assertEqualsRE(R.of(0), R.right(0)); 46 | }); 47 | 48 | Deno.test("ReaderEither ap", () => { 49 | assertEqualsRE(pipe(R.right(0), R.ap(R.of(AS.add))), R.right(1)); 50 | assertEqualsRE(pipe(R.right(0), R.ap(R.left(0))), R.left(0)); 51 | assertEqualsRE(pipe(R.left(0), R.ap(R.right(AS.add))), R.left(0)); 52 | assertEqualsRE(pipe(R.left(0), R.ap(R.left(1))), R.left(1)); 53 | }); 54 | 55 | Deno.test("ReaderEither join", () => { 56 | const tta = R.asks((n: number) => R.right(n)); 57 | assertEquals(R.join(tta)(0), E.right(0)); 58 | }); 59 | 60 | Deno.test("ReaderEither chain", () => { 61 | assertEqualsRE( 62 | pipe(R.right(0), R.chain((n) => n === 0 ? R.left(n) : R.right(n))), 63 | R.left(0), 64 | ); 65 | assertEqualsRE( 66 | pipe(R.right(1), R.chain((n) => n === 0 ? R.left(n) : R.right(n))), 67 | R.right(1), 68 | ); 69 | assertEqualsRE( 70 | pipe(R.left(1), R.chain((n) => n === 0 ? R.left(n) : R.right(n))), 71 | R.left(1), 72 | ); 73 | }); 74 | 75 | Deno.test("ReaderEither throwError", () => { 76 | assertEqualsRE(R.throwError(0), R.left(0)); 77 | }); 78 | 79 | Deno.test("ReaderEither bimap", () => { 80 | const bimap = R.bimap(AS.add, AS.multiply); 81 | assertEqualsRE(bimap(R.left(0)), R.left(1)); 82 | assertEqualsRE(bimap(R.right(2)), R.right(4)); 83 | }); 84 | 85 | Deno.test("ReaderEither mapLeft", () => { 86 | const mapLeft = R.mapLeft(AS.add); 87 | assertEqualsRE(mapLeft(R.left(0)), R.left(1)); 88 | assertEqualsRE(mapLeft(R.right(0)), R.right(0)); 89 | }); 90 | 91 | Deno.test("ReaderEither alt", () => { 92 | assertEqualsRE(pipe(R.right(0), R.alt(R.right(1))), R.right(0)); 93 | assertEqualsRE(pipe(R.right(0), R.alt(R.left(1))), R.right(0)); 94 | assertEqualsRE(pipe(R.left(0), R.alt(R.right(1))), R.right(1)); 95 | assertEqualsRE(pipe(R.left(0), R.alt(R.left(1))), R.left(1)); 96 | }); 97 | 98 | Deno.test("ReaderEither chainLeft", () => { 99 | const chainLeft = R.chainLeft((n) => n === 0 ? R.left(n) : R.right(n)); 100 | assertEqualsRE(chainLeft(R.right(0)), R.right(0)); 101 | assertEqualsRE(chainLeft(R.left(1)), R.right(1)); 102 | assertEqualsRE(chainLeft(R.left(0)), R.left(0)); 103 | }); 104 | 105 | Deno.test("ReaderEither widen", () => { 106 | assertEqualsRE(pipe(R.right(0), R.widen()), R.right(0)); 107 | assertEqualsRE(pipe(R.left(0), R.widen()), R.left(0)); 108 | }); 109 | 110 | Deno.test("ReaderEither Do, bind, bindTo", () => { 111 | assertEqualsRE( 112 | pipe( 113 | R.Do(), 114 | R.bind("one", () => R.right(1)), 115 | R.bind("two", ({ one }) => R.right(one + one)), 116 | R.map(({ one, two }) => one + two), 117 | ), 118 | R.right(3), 119 | ); 120 | assertEqualsRE( 121 | pipe( 122 | R.right(1), 123 | R.bindTo("one"), 124 | ), 125 | R.asks((_: number) => ({ one: 1 })), 126 | ); 127 | }); 128 | -------------------------------------------------------------------------------- /testing/record.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; 2 | 3 | import type * as HKT from "../hkt.ts"; 4 | 5 | import * as R from "../record.ts"; 6 | import * as O from "../option.ts"; 7 | import { setoidNumber } from "../setoid.ts"; 8 | import { monoidSum } from "../monoid.ts"; 9 | import { ordNumber } from "../ord.ts"; 10 | import { semigroupSum } from "../semigroup.ts"; 11 | import { pipe } from "../fns.ts"; 12 | 13 | import * as AS from "./assert.ts"; 14 | 15 | Deno.test("Record _map", () => { 16 | assertEquals(R._map(AS.add, { a: 1, b: 2 }), { a: 2, b: 3 }); 17 | assertEquals(R._map(AS.add, {}), {}); 18 | }); 19 | 20 | Deno.test("Record _reduce", () => { 21 | assertEquals( 22 | R._reduce((a: number, b: number) => a + b, 0, { a: 1, b: 2 }), 23 | 3, 24 | ); 25 | assertEquals(R._reduce(AS.add, 0, {}), 0); 26 | }); 27 | 28 | Deno.test("Record _assign", () => { 29 | assertEquals(R._assign("c")({ c: 1 })(2), { c: 2 }); 30 | }); 31 | 32 | Deno.test("Record Functor", () => { 33 | AS.assertFunctor(R.Functor, { 34 | ta: { a: 1, b: 2 }, 35 | fai: AS.add, 36 | fij: AS.multiply, 37 | }); 38 | }); 39 | 40 | Deno.test("Record IndexedFoldable", () => { 41 | AS.assertIndexedFoldable(R.IndexedFoldable, { 42 | a: 0, 43 | tb: { a: 1, b: 2 }, 44 | faia: (a: number, i: number) => a + i, 45 | }); 46 | }); 47 | 48 | Deno.test("Record Foldable", () => { 49 | AS.assertFoldable(R.Foldable, { 50 | a: 0, 51 | tb: { a: 1, b: 2 }, 52 | faia: (a: number, i: number) => a + i, 53 | }); 54 | }); 55 | 56 | Deno.test("Record getShow", () => { 57 | const { show } = R.getShow({ show: (n: number) => n.toString() }); 58 | assertEquals(show({}), "{}"); 59 | assertEquals(show({ a: 1, b: 2 }), "{a: 1, b: 2}"); 60 | }); 61 | 62 | Deno.test("Record traverse", () => { 63 | const t1 = R.traverse(O.Applicative); 64 | const t2 = t1((n: number) => n === 0 ? O.none : O.some(n)); 65 | assertEquals(t2({}), O.some({})); 66 | assertEquals(t2({ a: 0, b: 1 }), O.none); 67 | assertEquals(t2({ a: 1, b: 2 }), O.some({ a: 1, b: 2 })); 68 | }); 69 | 70 | Deno.test("Record reduce", () => { 71 | const reduce = R.reduce((a: number, c: number) => a + c, 0); 72 | assertEquals(reduce({}), 0); 73 | assertEquals(reduce({ a: 1, b: 2 }), 3); 74 | }); 75 | 76 | Deno.test("Record map", () => { 77 | const map = R.map(AS.add); 78 | assertEquals(map({}), {}); 79 | assertEquals(map({ a: 1, b: 2 }), { a: 2, b: 3 }); 80 | }); 81 | 82 | Deno.test("Record indexedTraverse", () => { 83 | const t1 = R.indexedTraverse(O.Applicative); 84 | const t2 = t1((a: number, i: string) => 85 | a === 0 ? O.some(i) : O.some(a.toString()) 86 | ); 87 | assertEquals(t2({}), O.some({})); 88 | assertEquals(t2({ a: 1, b: 2 }), O.some({ a: "1", b: "2" })); 89 | assertEquals(t2({ a: 0, b: 2 }), O.some({ a: "a", b: "2" })); 90 | }); 91 | 92 | Deno.test("Record indexedReduce", () => { 93 | const indexedReduce = R.indexedReduce( 94 | (o: string[], a: number, i: string) => 95 | a === 0 ? [...o, i] : [...o, a.toString()], 96 | [], 97 | ); 98 | assertEquals(indexedReduce({}), []); 99 | assertEquals(indexedReduce({ a: 1, b: 2 }), ["1", "2"]); 100 | assertEquals(indexedReduce({ a: 0, b: 2 }), ["a", "2"]); 101 | }); 102 | 103 | Deno.test("Record indexedMap", () => { 104 | const indexedMap = R.indexedMap((a: number, i: string) => 105 | a === 0 ? i.length : a + 1 106 | ); 107 | assertEquals(indexedMap({}), {}); 108 | assertEquals(indexedMap({ a: 1, b: 2 }), { a: 2, b: 3 }); 109 | assertEquals(indexedMap({ a: 0, b: 2 }), { a: 1, b: 3 }); 110 | }); 111 | 112 | Deno.test("Record insertAt", () => { 113 | assertEquals(pipe({ a: 1, b: 2 }, R.insertAt("b", 3)), { a: 1, b: 3 }); 114 | assertEquals(pipe({ a: 1, b: 2 }, R.insertAt("a", 1)), { a: 1, b: 2 }); 115 | }); 116 | 117 | Deno.test("Record deleteAt", () => { 118 | assertEquals(pipe({ a: 1, b: 2 }, R.deleteAt("a")), { b: 2 }); 119 | assertEquals(pipe({ a: 1 } as Record, R.deleteAt("b")), { 120 | a: 1, 121 | }); 122 | }); 123 | 124 | Deno.test("Record omit", () => { 125 | assertEquals(R.omit(["a", "b"], { a: 1, b: 2, c: 3 }), { c: 3 }); 126 | }); 127 | 128 | Deno.test("Record pick", () => { 129 | assertEquals(pipe({ a: 1, b: 2, c: 3 }, R.pick("a", "b")), { a: 1, b: 2 }); 130 | }); 131 | 132 | Deno.test("Record keys", () => { 133 | assertEquals(R.keys({}), []); 134 | }); 135 | 136 | Deno.test("Record zipFirst", () => { 137 | const zipFirst = R.zipFirst((k: string, a: number, b: unknown) => [k, a, b]); 138 | const a1 = zipFirst({ a: 1, b: 2 }); 139 | assertEquals(a1({ a: "Hello" }), { 140 | a: ["a", 1, "Hello"], 141 | b: ["b", 2, undefined], 142 | }); 143 | }); 144 | -------------------------------------------------------------------------------- /testing/schemable/decode_error.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; 2 | 3 | import * as D from "../../schemable/decode_error.ts"; 4 | import { draw } from "../../schemable/decoder.ts"; 5 | import { Free } from "../../semigroup.ts"; 6 | import { pipe } from "../../fns.ts"; 7 | 8 | const { leaf, key, index, member, lazy, wrap } = D.make; 9 | 10 | Deno.test("DecodeError leaf", () => { 11 | assertEquals(leaf(1, 1), { 12 | tag: "Of", 13 | value: { tag: "Leaf", actual: 1, error: 1 }, 14 | }); 15 | }); 16 | 17 | Deno.test("DecodeError key", () => { 18 | assertEquals(key("one", D.required, leaf(1, 1)), { 19 | tag: "Of", 20 | value: { 21 | tag: "Key", 22 | key: "one", 23 | kind: D.required, 24 | errors: { 25 | tag: "Of", 26 | value: { 27 | tag: "Leaf", 28 | actual: 1, 29 | error: 1, 30 | }, 31 | }, 32 | }, 33 | }); 34 | }); 35 | 36 | Deno.test("DecodeError index", () => { 37 | assertEquals(index(0, D.optional, leaf(1, 1)), { 38 | tag: "Of", 39 | value: { 40 | tag: "Index", 41 | index: 0, 42 | kind: D.optional, 43 | errors: { 44 | tag: "Of", 45 | value: { tag: "Leaf", actual: 1, error: 1 }, 46 | }, 47 | }, 48 | }); 49 | }); 50 | 51 | Deno.test("DecodeError member", () => { 52 | assertEquals(member(0, leaf(1, 1)), { 53 | tag: "Of", 54 | value: { 55 | tag: "Member", 56 | index: 0, 57 | errors: { 58 | tag: "Of", 59 | value: { tag: "Leaf", actual: 1, error: 1 }, 60 | }, 61 | }, 62 | }); 63 | }); 64 | 65 | Deno.test("DecodeError lazy", () => { 66 | assertEquals(lazy("one", leaf(1, 1)), { 67 | tag: "Of", 68 | value: { 69 | tag: "Lazy", 70 | id: "one", 71 | errors: { 72 | tag: "Of", 73 | value: { tag: "Leaf", actual: 1, error: 1 }, 74 | }, 75 | }, 76 | }); 77 | }); 78 | 79 | Deno.test("DecodeError wrap", () => { 80 | assertEquals(wrap(1, leaf(1, 1)), { 81 | tag: "Of", 82 | value: { 83 | tag: "Wrap", 84 | error: 1, 85 | errors: { 86 | tag: "Of", 87 | value: { tag: "Leaf", actual: 1, error: 1 }, 88 | }, 89 | }, 90 | }); 91 | }); 92 | 93 | Deno.test("DecodeError fold", () => { 94 | assertEquals(draw(leaf(1, "1")), "cannot decode 1, should be 1"); 95 | assertEquals( 96 | draw(key("one", D.required, leaf(1, "1"))), 97 | 'required property "one"\n└─ cannot decode 1, should be 1', 98 | ); 99 | assertEquals( 100 | draw(index(1, D.required, leaf(1, "1"))), 101 | "required index 1\n└─ cannot decode 1, should be 1", 102 | ); 103 | assertEquals( 104 | draw(member(0, leaf(1, "1"))), 105 | "member 0\n└─ cannot decode 1, should be 1", 106 | ); 107 | assertEquals( 108 | draw(lazy("One", leaf(1, "1"))), 109 | "lazy type One\n└─ cannot decode 1, should be 1", 110 | ); 111 | assertEquals( 112 | draw(wrap("2", leaf(1, "1"))), 113 | "2\n└─ cannot decode 1, should be 1", 114 | ); 115 | assertEquals( 116 | draw(pipe(leaf(1, "1"), Free.concat(leaf(2, "2")))), 117 | "cannot decode 2, should be 2\ncannot decode 1, should be 1", 118 | ); 119 | }); 120 | 121 | Deno.test("DecodeError getSemigroup", () => { 122 | const { concat } = D.getSemigroup(); 123 | assertEquals( 124 | pipe(leaf(1, "1"), concat(leaf(2, "2"))), 125 | { 126 | left: { 127 | tag: "Of", 128 | value: { 129 | actual: 2, 130 | error: "2", 131 | tag: "Leaf", 132 | }, 133 | }, 134 | right: { 135 | tag: "Of", 136 | value: { 137 | actual: 1, 138 | error: "1", 139 | tag: "Leaf", 140 | }, 141 | }, 142 | tag: "Concat", 143 | }, 144 | ); 145 | }); 146 | 147 | 148 | -------------------------------------------------------------------------------- /testing/schemable/decoder.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; 2 | 3 | import * as D from "../../schemable/decoder.ts"; 4 | import * as G from "../../schemable/guard.ts"; 5 | import * as DE from "../../schemable/decode_error.ts"; 6 | import * as E from "../../either.ts"; 7 | 8 | Deno.test("Decoder success", () => { 9 | assertEquals(D.success(1), E.right(1)); 10 | }); 11 | 12 | Deno.test("Decoder failure", () => { 13 | assertEquals(D.failure(1, "1"), E.left(DE.make.leaf(1, "1"))); 14 | }); 15 | 16 | Deno.test("Decoder fromGuard", () => { 17 | const decoder = D.fromGuard(G.number, "number"); 18 | assertEquals(decoder(0), D.success(0)); 19 | assertEquals(decoder(true), D.failure(true, "number")); 20 | }); 21 | 22 | Deno.test("Decoder unknown", () => { 23 | assertEquals(D.unknown(1), D.success(1)); 24 | }); 25 | 26 | Deno.test("Decoder string", () => { 27 | assertEquals(D.string("asdf"), D.success("asdf")); 28 | assertEquals(D.string(1), D.failure(1, "string")); 29 | }); 30 | 31 | Deno.test("Decoder number", () => { 32 | assertEquals(D.number(1), D.success(1)); 33 | assertEquals(D.number(false), D.failure(false, "number")); 34 | }); 35 | 36 | Deno.test("Decoder boolean", () => { 37 | assertEquals(D.boolean(true), D.success(true)); 38 | assertEquals(D.boolean(false), D.success(false)); 39 | assertEquals(D.boolean(0), D.failure(0, "boolean")); 40 | }); 41 | 42 | Deno.test("Decoder literal", () => { 43 | const literal = D.literal(1, 2, 3); 44 | assertEquals(literal(1), D.success(1)); 45 | assertEquals(literal(false), D.failure(false, "literal 1, 2, or 3")); 46 | }); 47 | 48 | Deno.test("Decoder nullable", () => { 49 | const decoder = D.undefinable(D.number); 50 | assertEquals(decoder(0), D.success(0)); 51 | assertEquals(decoder(undefined), D.success(undefined)); 52 | // assertEquals(decoder(true), D.failure(true, "")); 53 | }); 54 | -------------------------------------------------------------------------------- /testing/schemable/guard.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; 2 | 3 | import * as G from "../../schemable/guard.ts"; 4 | import { pipe } from "../../fns.ts"; 5 | 6 | Deno.test("Guard unknown", () => { 7 | assertEquals(G.unknown(1), true); 8 | }); 9 | 10 | Deno.test("Guard string", () => { 11 | assertEquals(G.string("asdf"), true); 12 | assertEquals(G.string(1), false); 13 | }); 14 | 15 | Deno.test("Guard number", () => { 16 | assertEquals(G.number("asdf"), false); 17 | assertEquals(G.number(1), true); 18 | }); 19 | 20 | Deno.test("Guard boolean", () => { 21 | assertEquals(G.boolean(true), true); 22 | assertEquals(G.boolean(false), true); 23 | assertEquals(G.boolean("asdf"), false); 24 | }); 25 | 26 | Deno.test("Guard literal", () => { 27 | const literal = G.literal(1, 2, 3); 28 | assertEquals(literal(1), true); 29 | assertEquals(literal(2), true); 30 | assertEquals(literal(3), true); 31 | assertEquals(literal(4), false); 32 | }); 33 | 34 | Deno.test("Guard undefinable", () => { 35 | const guard = G.undefinable(G.literal(1)); 36 | assertEquals(guard(1), true); 37 | assertEquals(guard(undefined), true); 38 | assertEquals(guard(null), false); 39 | }); 40 | 41 | Deno.test("Guard nullable", () => { 42 | const guard = G.nullable(G.literal(1)); 43 | assertEquals(guard(1), true); 44 | assertEquals(guard(undefined), false); 45 | assertEquals(guard(null), true); 46 | }); 47 | 48 | Deno.test("Guard record", () => { 49 | const guard = G.record(G.number); 50 | assertEquals(guard({}), true); 51 | assertEquals(guard({ "one": 1 }), true); 52 | assertEquals(guard({ "one": false }), false); 53 | assertEquals(guard(1), false); 54 | }); 55 | 56 | Deno.test("Guard array", () => { 57 | const guard = G.array(G.boolean); 58 | assertEquals(guard(true), false); 59 | assertEquals(guard([]), true); 60 | assertEquals(guard([true]), true); 61 | assertEquals(guard(["asdf"]), false); 62 | }); 63 | 64 | Deno.test("Guard tuple", () => { 65 | const guard = G.tuple(G.literal("Left", "Right"), G.number); 66 | assertEquals(guard(["Left", 1]), true); 67 | assertEquals(guard(["Right", 1]), true); 68 | assertEquals(guard(false), false); 69 | assertEquals(guard(["Left"]), false); 70 | }); 71 | 72 | Deno.test("Guard struct", () => { 73 | const guard = G.struct({ one: G.number }); 74 | assertEquals(guard({}), false); 75 | assertEquals(guard(1), false); 76 | assertEquals(guard({ one: false }), false); 77 | assertEquals(guard({ one: 1 }), true); 78 | }); 79 | 80 | Deno.test("Guard partial", () => { 81 | const guard = G.partial({ one: G.number }); 82 | assertEquals(guard({}), true); 83 | assertEquals(guard(1), false); 84 | assertEquals(guard({ one: false }), false); 85 | assertEquals(guard({ one: 1 }), true); 86 | }); 87 | 88 | Deno.test("Guard intersect", () => { 89 | const guard = pipe( 90 | G.struct({ two: G.boolean }), 91 | G.intersect(G.struct({ one: G.number })), 92 | ); 93 | assertEquals(guard({}), false); 94 | assertEquals(guard(1), false); 95 | assertEquals(guard({ two: true }), false); 96 | assertEquals(guard({ one: 1 }), false); 97 | assertEquals(guard({ one: 1, two: true }), true); 98 | }); 99 | 100 | Deno.test("Guard union", () => { 101 | const guard = pipe( 102 | G.number, 103 | G.union(G.boolean), 104 | ); 105 | assertEquals(guard(null), false); 106 | assertEquals(guard("asdf"), false); 107 | assertEquals(guard(1), true); 108 | assertEquals(guard(true), true); 109 | }); 110 | 111 | Deno.test("Guard lazy", () => { 112 | const guard = G.lazy("One", () => G.number); 113 | assertEquals(guard(1), true); 114 | assertEquals(guard(true), false); 115 | }); 116 | -------------------------------------------------------------------------------- /testing/semigroup.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; 2 | 3 | import * as AS from "./assert.ts"; 4 | 5 | import * as S from "../semigroup.ts"; 6 | import { ordNumber } from "../ord.ts"; 7 | 8 | const toArray: (a: S.Free) => number[] = S.Free.fold( 9 | (value: number) => [value], 10 | (left, right) => toArray(left).concat(toArray(right)), 11 | ); 12 | 13 | Deno.test("Semigroup Free", () => { 14 | const ta = S.Free.of(1); 15 | const tb = S.Free.concat(ta)(ta); 16 | assertEquals(ta, { tag: "Of", value: 1 }); 17 | assertEquals(tb, { tag: "Concat", left: ta, right: ta }); 18 | assertEquals(toArray(ta), [1]); 19 | assertEquals(toArray(tb), [1, 1]); 20 | }); 21 | 22 | Deno.test("Semigroup semigroupAll", () => { 23 | const concat = S.semigroupAll.concat; 24 | assertEquals(concat(true)(true), true); 25 | assertEquals(concat(true)(false), false); 26 | assertEquals(concat(false)(true), false); 27 | assertEquals(concat(false)(false), false); 28 | }); 29 | 30 | Deno.test("Semigroup semigroupAny", () => { 31 | const concat = S.semigroupAny.concat; 32 | assertEquals(concat(true)(true), true); 33 | assertEquals(concat(true)(false), true); 34 | assertEquals(concat(false)(true), true); 35 | assertEquals(concat(false)(false), false); 36 | }); 37 | 38 | Deno.test("Semigroup semigroupSum", () => { 39 | const concat = S.semigroupSum.concat; 40 | assertEquals(concat(1)(1), 2); 41 | assertEquals(concat(0)(1), 1); 42 | assertEquals(concat(1)(0), 1); 43 | assertEquals(concat(0)(0), 0); 44 | }); 45 | 46 | Deno.test("Semigroup semigroupProduct", () => { 47 | const concat = S.semigroupProduct.concat; 48 | assertEquals(concat(2)(2), 4); 49 | }); 50 | 51 | Deno.test("Semigroup semigroupString", () => { 52 | const concat = S.semigroupString.concat; 53 | assertEquals(concat("Hello")("World"), "HelloWorld"); 54 | }); 55 | 56 | Deno.test("Semigroup semigroupVoid", () => { 57 | const concat = S.semigroupVoid.concat; 58 | assertEquals(concat(undefined)(undefined), undefined); 59 | }); 60 | 61 | Deno.test("Semigroup getFreeSemigroup", () => { 62 | const { concat } = S.getFreeSemigroup(); 63 | const ta = S.Free.of(1); 64 | const tb = S.Free.of(2); 65 | const tc = concat(ta)(tb); 66 | const td = concat(tc)(tb); 67 | assertEquals(toArray(td), [1, 2, 2]); 68 | }); 69 | 70 | Deno.test("Semigroup getFirstSemigroup", () => { 71 | const { concat } = S.getFirstSemigroup(); 72 | assertEquals(concat(1)(2), 1); 73 | }); 74 | 75 | Deno.test("Semigroup getLastSemigroup", () => { 76 | const { concat } = S.getLastSemigroup(); 77 | assertEquals(concat(1)(2), 2); 78 | }); 79 | 80 | Deno.test("Semigroup getTupleSemigroup", () => { 81 | const { concat } = S.getTupleSemigroup(S.semigroupSum, S.semigroupString); 82 | assertEquals(concat([1, "Hello "])([2, "World"]), [3, "Hello World"]); 83 | }); 84 | 85 | Deno.test("Semigroup getDualSemigroup", () => { 86 | const { concat } = S.getDualSemigroup(S.getFirstSemigroup()); 87 | assertEquals(concat(1)(2), 2); 88 | }); 89 | 90 | Deno.test("Semigroup getStrucSemigroup", () => { 91 | const { concat } = S.getStructSemigroup({ 92 | a: S.semigroupSum, 93 | b: S.semigroupAll, 94 | }); 95 | assertEquals(concat({ a: 1, b: true })({ a: 2, b: false }), { 96 | a: 3, 97 | b: false, 98 | }); 99 | }); 100 | 101 | Deno.test("Semigroup getMeetSemigroup", () => { 102 | const { concat } = S.getMeetSemigroup(ordNumber); 103 | assertEquals(concat(0)(0), 0); 104 | assertEquals(concat(0)(1), 0); 105 | assertEquals(concat(1)(0), 0); 106 | assertEquals(concat(1)(1), 1); 107 | }); 108 | 109 | Deno.test("Semigroup getJoinSemigroup", () => { 110 | const { concat } = S.getJoinSemigroup(ordNumber); 111 | assertEquals(concat(0)(0), 0); 112 | assertEquals(concat(0)(1), 1); 113 | assertEquals(concat(1)(0), 1); 114 | assertEquals(concat(1)(1), 1); 115 | }); 116 | 117 | Deno.test("Semigroup fold", () => { 118 | const fold = S.fold(S.semigroupSum)(0); 119 | assertEquals(fold([]), 0); 120 | assertEquals(fold([1, 2, 3]), 6); 121 | }); 122 | -------------------------------------------------------------------------------- /testing/set.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; 2 | 3 | import * as AS from "./assert.ts"; 4 | 5 | import * as S from "../set.ts"; 6 | import * as O from "../option.ts"; 7 | import { setoidNumber } from "../setoid.ts"; 8 | import { monoidSum } from "../monoid.ts"; 9 | import { ordNumber } from "../ord.ts"; 10 | import { semigroupSum } from "../semigroup.ts"; 11 | import { pipe } from "../fns.ts"; 12 | 13 | Deno.test("Set zero", () => { 14 | assertEquals(S.zero, new Set()); 15 | }); 16 | 17 | Deno.test("Set empty", () => { 18 | assertEquals(S.empty(), S.zero); 19 | }); 20 | 21 | Deno.test("Set make", () => { 22 | assertEquals(S.make(1), new Set([1])); 23 | }); 24 | 25 | Deno.test("Set elem", () => { 26 | const elem = S.elem(setoidNumber); 27 | assertEquals(pipe(S.make(1), elem(1)), true); 28 | assertEquals(pipe(S.make(1), elem(2)), false); 29 | }); 30 | 31 | Deno.test("Set elemOf", () => { 32 | const elemOf = S.elemOf(setoidNumber); 33 | assertEquals(pipe(1, elemOf(S.make(1))), true); 34 | assertEquals(pipe(2, elemOf(S.make(1))), false); 35 | }); 36 | 37 | Deno.test("Set isSubset", () => { 38 | const isSubset = S.isSubset(setoidNumber); 39 | const ta = S.make(1); 40 | const tb = S.make(1); 41 | tb.add(2); 42 | assertEquals(pipe(ta, isSubset(tb)), true); 43 | assertEquals(pipe(tb, isSubset(ta)), false); 44 | }); 45 | 46 | Deno.test("Set union", () => { 47 | const union = S.union(setoidNumber); 48 | assertEquals(pipe(S.make(1), union(S.make(2))), S.make(1, 2)); 49 | }); 50 | 51 | Deno.test("Set intersection", () => { 52 | const intersection = S.intersection(setoidNumber); 53 | assertEquals(pipe(S.make(1), intersection(S.make(2))), S.empty()); 54 | assertEquals(pipe(S.make(1, 2), intersection(S.make(2, 3))), S.make(2)); 55 | }); 56 | 57 | Deno.test("Set compact", () => { 58 | const compact = S.compact(setoidNumber); 59 | assertEquals(compact(S.make(1, 2, 3)), S.make(1, 2, 3)); 60 | }); 61 | 62 | Deno.test("Set join", () => { 63 | assertEquals(S.join(S.make(S.make(1, 2), S.make(2, 3))), S.make(1, 2, 3)); 64 | }); 65 | 66 | Deno.test("Set Functor", () => { 67 | AS.assertFunctor(S.Functor, { 68 | ta: S.make(1, 2, 3), 69 | fai: AS.add, 70 | fij: AS.multiply, 71 | }); 72 | }); 73 | 74 | Deno.test("Set Apply", () => { 75 | AS.assertApply(S.Apply, { 76 | ta: S.make(1, 2, 3), 77 | fai: AS.add, 78 | fij: AS.multiply, 79 | tfai: S.make(AS.add, AS.multiply), 80 | tfij: S.make(AS.multiply, AS.add), 81 | }); 82 | }); 83 | 84 | Deno.test("Set Filterable", () => { 85 | AS.assertFilterable(S.Filterable, { 86 | a: S.make(1, 2, 3), 87 | b: S.make(2, 3, 4), 88 | f: (n: number) => n < 2, 89 | g: (n: number) => n > 4, 90 | }); 91 | }); 92 | 93 | Deno.test("Set Foldable", () => { 94 | AS.assertFoldable(S.Foldable, { 95 | a: 0, 96 | tb: S.make(1, 2, 3), 97 | faia: (n: number, i: number) => n + i, 98 | }); 99 | }); 100 | 101 | Deno.test("Set getShow", () => { 102 | const { show } = S.getShow({ show: (n: number) => n.toString() }); 103 | assertEquals(show(S.empty()), "Set([])"); 104 | assertEquals(show(S.make(1, 2, 3)), "Set([1, 2, 3])"); 105 | }); 106 | 107 | Deno.test("Set getSetoid", () => { 108 | const Setoid = S.getSetoid(setoidNumber); 109 | AS.assertSetoid(Setoid, { 110 | a: S.make(1), 111 | b: S.make(1), 112 | c: S.make(1), 113 | z: S.make(1, 2, 3), 114 | }); 115 | }); 116 | 117 | Deno.test("Set getUnionMonoid", () => { 118 | const Monoid = S.getUnionMonoid(setoidNumber); 119 | AS.assertMonoid(Monoid, { 120 | a: S.make(1, 2), 121 | b: S.make(2, 3), 122 | c: S.make(3, 4), 123 | }); 124 | }); 125 | 126 | Deno.test("Set filter", () => { 127 | const filter = S.filter((n: number) => n > 0); 128 | assertEquals(filter(S.make(1, 2, 3)), S.make(1, 2, 3)); 129 | assertEquals(filter(S.make(-1, 0, 1)), S.make(1)); 130 | }); 131 | 132 | Deno.test("Set map", () => { 133 | assertEquals(pipe(S.make(1, 2, 3), S.map(AS.add)), S.make(2, 3, 4)); 134 | }); 135 | 136 | Deno.test("Set reduce", () => { 137 | const reduce = S.reduce((n: number, o: number) => n + o, 0); 138 | assertEquals(reduce(S.zero), 0); 139 | assertEquals(reduce(S.make(1, 2, 3)), 6); 140 | }); 141 | 142 | Deno.test("Set traverse", () => { 143 | const t1 = S.traverse(O.Applicative); 144 | const t2 = t1((n: number) => n === 0 ? O.none : O.some(n)); 145 | assertEquals(t2(S.empty()), O.some(S.empty())); 146 | assertEquals(t2(S.make(1, 2, 3)), O.some(S.make(1, 2, 3))); 147 | assertEquals(t2(S.make(0, 1, 2)), O.none); 148 | }); 149 | -------------------------------------------------------------------------------- /testing/setoid.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; 2 | 3 | import * as AS from "./assert.ts"; 4 | 5 | import * as S from "../setoid.ts"; 6 | import { pipe } from "../fns.ts"; 7 | 8 | Deno.test("Setoid fromEquals", () => { 9 | const Setoid = S.fromEquals((a: number) => (b: number) => a === b); 10 | AS.assertSetoid(Setoid, { a: 1, b: 1, c: 1, z: 2 }); 11 | }); 12 | 13 | Deno.test("Setoid setoidStrict", () => { 14 | AS.assertSetoid(S.setoidStrict, { a: 1, b: 1, c: 1, z: 2 }); 15 | }); 16 | 17 | Deno.test("Setoid setoidString", () => { 18 | AS.assertSetoid(S.setoidString, { a: "a", b: "a", c: "a", z: "b" }); 19 | }); 20 | 21 | Deno.test("Setoid setoidNumber", () => { 22 | AS.assertSetoid(S.setoidNumber, { a: 1, b: 1, c: 1, z: 2 }); 23 | }); 24 | 25 | Deno.test("Setoid setoidBoolean", () => { 26 | AS.assertSetoid(S.setoidBoolean, { a: true, b: true, c: true, z: false }); 27 | }); 28 | 29 | Deno.test("Setoid setoidDate", () => { 30 | const ta = new Date(0); 31 | const tb = new Date(1); 32 | AS.assertSetoid(S.setoidDate, { a: ta, b: ta, c: ta, z: tb }); 33 | }); 34 | 35 | Deno.test("Setoid getStructSetoid", () => { 36 | const Setoid = S.getStructSetoid({ a: S.setoidString, b: S.setoidNumber }); 37 | AS.assertSetoid(Setoid, { 38 | a: { a: "a", b: 1 }, 39 | b: { a: "a", b: 1 }, 40 | c: { a: "a", b: 1 }, 41 | z: { a: "b", b: 2 }, 42 | }); 43 | }); 44 | 45 | Deno.test("Setoid getTupleSetoid", () => { 46 | const Setoid = S.getTupleSetoid(S.setoidNumber, S.setoidBoolean); 47 | AS.assertSetoid(Setoid, { 48 | a: [1, true], 49 | b: [1, true], 50 | c: [1, true], 51 | z: [2, false], 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /testing/state.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; 2 | 3 | import * as S from "../state.ts"; 4 | import { pipe } from "../fns.ts"; 5 | 6 | import * as AS from "./assert.ts"; 7 | 8 | const assertEqualsS = ( 9 | a: S.State, 10 | b: S.State, 11 | ) => assertEquals(a(1), b(1)); 12 | 13 | Deno.test("State get", () => { 14 | assertEquals(S.get()(0), [0, 0]); 15 | }); 16 | 17 | Deno.test("State put", () => { 18 | assertEquals(S.put(0)(-1), [undefined, 0]); 19 | }); 20 | 21 | Deno.test("State modify", () => { 22 | assertEqualsS(S.modify((n: number) => n + 1), S.put(2)); 23 | }); 24 | 25 | Deno.test("State gets", () => { 26 | assertEquals(S.gets((n: number) => n.toString())(0), ["0", 0]); 27 | }); 28 | 29 | Deno.test("State make", () => { 30 | assertEqualsS(S.make(1, 1), S.get()); 31 | }); 32 | 33 | Deno.test("State of", () => { 34 | assertEqualsS(S.of(1), S.get()); 35 | }); 36 | 37 | Deno.test("State ap", () => { 38 | const ap = S.ap(S.gets((s: number): ((a: number) => number) => (a) => a + s)); 39 | assertEqualsS(ap(S.get()), S.make(2, 1)); 40 | }); 41 | 42 | Deno.test("State map", () => { 43 | assertEqualsS(pipe(S.get(), S.map(AS.add)), S.make(2, 1)); 44 | }); 45 | 46 | Deno.test("State join", () => { 47 | assertEqualsS( 48 | S.join(S.gets((n: number) => S.gets((m: number) => n + m))), 49 | S.make(2, 1), 50 | ); 51 | }); 52 | 53 | Deno.test("State chain", () => { 54 | assertEqualsS( 55 | pipe(S.get(), S.chain((n) => S.gets((m) => n + m))), 56 | S.make(2, 1), 57 | ); 58 | }); 59 | 60 | Deno.test("State evaluate", () => { 61 | assertEquals(pipe(S.get(), S.evaluate(0)), 0); 62 | }); 63 | 64 | Deno.test("State execute", () => { 65 | assertEquals(pipe(S.get(), S.evaluate(0)), 0); 66 | }); 67 | 68 | Deno.test("State Do, bind, bindTo", () => { 69 | assertEqualsS( 70 | pipe( 71 | S.Do(), 72 | S.bind("one", () => S.make(1, 1)), 73 | S.bind("two", ({ one }) => S.make(one + one, 1)), 74 | S.map(({ one, two }) => one + two), 75 | ), 76 | S.make(3, 1), 77 | ); 78 | assertEqualsS( 79 | pipe( 80 | S.make(1, 1), 81 | S.bindTo("one"), 82 | ), 83 | S.make({ one: 1 }, 1), 84 | ); 85 | }); 86 | -------------------------------------------------------------------------------- /testing/task.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; 2 | 3 | import * as T from "../task.ts"; 4 | import { _, pipe } from "../fns.ts"; 5 | 6 | import * as AS from "./assert.ts"; 7 | 8 | const assertEqualsT = async (a: T.Task, b: T.Task) => 9 | assertEquals(await a(), await b()); 10 | 11 | Deno.test("Task make", async () => { 12 | await assertEqualsT(T.make(0), T.make(0)); 13 | }); 14 | 15 | Deno.test("Task delay", async () => { 16 | await assertEqualsT(pipe(T.make(0), T.delay(200)), T.make(0)); 17 | }); 18 | 19 | Deno.test("Task fromThunk", async () => { 20 | await assertEqualsT(T.fromThunk(() => 0), T.make(0)); 21 | }); 22 | 23 | Deno.test("Task tryCatch", async () => { 24 | await assertEqualsT(T.tryCatch(_, () => 0), T.make(0)); 25 | await assertEqualsT(T.tryCatch(() => 1, () => 0), T.make(1)); 26 | }); 27 | 28 | Deno.test("Task of", async () => { 29 | await assertEqualsT(T.of(1), T.make(1)); 30 | }); 31 | 32 | Deno.test("Task ap", async () => { 33 | await assertEqualsT(pipe(T.of(1), T.ap(T.of(AS.add))), T.of(2)); 34 | }); 35 | 36 | Deno.test("Task map", async () => { 37 | await assertEqualsT(pipe(T.of(1), T.map(AS.add)), T.of(2)); 38 | }); 39 | 40 | Deno.test("Task join", async () => { 41 | await assertEqualsT(T.join(T.of(T.of(1))), T.of(1)); 42 | }); 43 | 44 | Deno.test("Task chain", async () => { 45 | await assertEqualsT(pipe(T.of(1), T.chain((n) => T.of(n + 1))), T.of(2)); 46 | }); 47 | 48 | Deno.test("Task apSeq", async () => { 49 | await assertEqualsT(pipe(T.of(1), T.apSeq(T.of(AS.add))), T.of(2)); 50 | }); 51 | 52 | Deno.test("Task Do, bind, bindTo", () => { 53 | assertEqualsT( 54 | pipe( 55 | T.Do(), 56 | T.bind("one", () => T.make(1)), 57 | T.bind("two", ({ one }) => T.make(one + one)), 58 | T.map(({ one, two }) => one + two), 59 | ), 60 | T.make(3), 61 | ); 62 | assertEqualsT( 63 | pipe( 64 | T.make(1), 65 | T.bindTo("one"), 66 | ), 67 | T.make({ one: 1 }), 68 | ); 69 | }); 70 | -------------------------------------------------------------------------------- /testing/task_either.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; 2 | 3 | import * as T from "../task_either.ts"; 4 | import * as E from "../either.ts"; 5 | import { _, pipe } from "../fns.ts"; 6 | 7 | import * as AS from "./assert.ts"; 8 | 9 | const assertEqualsT = async ( 10 | a: T.TaskEither, 11 | b: T.TaskEither, 12 | ) => assertEquals(await a(), await b()); 13 | 14 | Deno.test("TaskEither left", async () => { 15 | await assertEqualsT(T.left(1), T.left(1)); 16 | }); 17 | 18 | Deno.test("TaskEither right", async () => { 19 | await assertEqualsT(T.right(1), T.right(1)); 20 | }); 21 | 22 | Deno.test("TaskEither tryCatch", async () => { 23 | await assertEqualsT(T.tryCatch(_, () => "Bad"), T.left("Bad")); 24 | await assertEqualsT(T.tryCatch(() => 1, String), T.right(1)); 25 | }); 26 | 27 | Deno.test("TaskEither fromFailableTask", async () => { 28 | const fromFailableTask = T.fromFailableTask(() => "Bad"); 29 | await assertEqualsT(fromFailableTask(() => Promise.reject()), T.left("Bad")); 30 | await assertEqualsT(fromFailableTask(() => Promise.resolve(1)), T.right(1)); 31 | }); 32 | 33 | Deno.test("TaskEither fromEither", async () => { 34 | await assertEqualsT(T.fromEither(E.left(1)), T.left(1)); 35 | await assertEqualsT(T.fromEither(E.right(1)), T.right(1)); 36 | }); 37 | 38 | Deno.test("TaskEither then", async () => { 39 | assertEquals( 40 | await pipe(Promise.resolve(1), T.then(AS.add)), 41 | await Promise.resolve(2), 42 | ); 43 | }); 44 | 45 | Deno.test("TaskEither of", async () => { 46 | await assertEqualsT(T.of(1), T.of(1)); 47 | }); 48 | 49 | Deno.test("TaskEither ap", async () => { 50 | await assertEqualsT(pipe(T.right(1), T.ap(T.right(AS.add))), T.right(2)); 51 | await assertEqualsT(pipe(T.left(1), T.ap(T.right(AS.add))), T.left(1)); 52 | await assertEqualsT(pipe(T.right(1), T.ap(T.left(1))), T.left(1)); 53 | await assertEqualsT(pipe(T.left(1), T.ap(T.left(2))), T.left(1)); 54 | }); 55 | 56 | Deno.test("TaskEither map", async () => { 57 | await assertEqualsT(pipe(T.right(1), T.map(AS.add)), T.right(2)); 58 | await assertEqualsT(pipe(T.left(1), T.map(AS.add)), T.left(1)); 59 | }); 60 | 61 | Deno.test("TaskEither join", async () => { 62 | await assertEqualsT(T.join(T.right(T.right(1))), T.right(1)); 63 | await assertEqualsT(T.join(T.right(T.left(1))), T.left(1)); 64 | await assertEqualsT(T.join(T.left(1)), T.left(1)); 65 | }); 66 | 67 | Deno.test("TaskEither chain", async () => { 68 | const chain = T.chain((n: number) => n === 0 ? T.left(0) : T.right(1)); 69 | await assertEqualsT(chain(T.right(0)), T.left(0)); 70 | await assertEqualsT(chain(T.right(1)), T.right(1)); 71 | await assertEqualsT(chain(T.left(1)), T.left(1)); 72 | }); 73 | 74 | Deno.test("TaskEither bimap", async () => { 75 | const bimap = T.bimap(AS.add, AS.add); 76 | await assertEqualsT(bimap(T.right(1)), T.right(2)); 77 | await assertEqualsT(bimap(T.left(1)), T.left(2)); 78 | }); 79 | 80 | Deno.test("TaskEither mapLeft", async () => { 81 | await assertEqualsT(pipe(T.right(1), T.mapLeft(AS.add)), T.right(1)); 82 | await assertEqualsT(pipe(T.left(1), T.mapLeft(AS.add)), T.left(2)); 83 | }); 84 | 85 | Deno.test("TaskEither apSeq", async () => { 86 | await assertEqualsT(pipe(T.right(1), T.apSeq(T.right(AS.add))), T.right(2)); 87 | await assertEqualsT(pipe(T.left(1), T.apSeq(T.right(AS.add))), T.left(1)); 88 | await assertEqualsT(pipe(T.right(1), T.apSeq(T.left(1))), T.left(1)); 89 | await assertEqualsT(pipe(T.left(1), T.apSeq(T.left(2))), T.left(1)); 90 | }); 91 | 92 | Deno.test("TaskEither chainLeft", async () => { 93 | const chainLeft = T.chainLeft((n: number) => 94 | n === 0 ? T.right(n) : T.left(n) 95 | ); 96 | await assertEqualsT(chainLeft(T.right(0)), T.right(0)); 97 | await assertEqualsT(chainLeft(T.left(0)), T.right(0)); 98 | await assertEqualsT(chainLeft(T.left(1)), T.left(1)); 99 | }); 100 | 101 | Deno.test("TaskEither widen", async () => { 102 | await assertEqualsT(pipe(T.right(1), T.widen()), T.right(1)); 103 | }); 104 | 105 | Deno.test("TaskEither Do, bind, bindTo", () => { 106 | assertEqualsT( 107 | pipe( 108 | T.Do(), 109 | T.bind("one", () => T.right(1)), 110 | T.bind("two", ({ one }) => T.right(one + one)), 111 | T.map(({ one, two }) => one + two), 112 | ), 113 | T.right(3), 114 | ); 115 | assertEqualsT( 116 | pipe( 117 | T.right(1), 118 | T.bindTo("one"), 119 | ), 120 | T.right({ one: 1 }), 121 | ); 122 | }); 123 | -------------------------------------------------------------------------------- /testing/these.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; 2 | 3 | import * as T from "../these.ts"; 4 | import * as O from "../option.ts"; 5 | import { _, pipe } from "../fns.ts"; 6 | import { semigroupSum } from "../semigroup.ts"; 7 | 8 | import * as AS from "./assert.ts"; 9 | 10 | Deno.test("These left", () => { 11 | assertEquals(T.left(1), { tag: "Left", left: 1 }); 12 | }); 13 | 14 | Deno.test("These right", () => { 15 | assertEquals(T.right(1), { tag: "Right", right: 1 }); 16 | }); 17 | 18 | Deno.test("These both", () => { 19 | assertEquals(T.both(0, 1), { tag: "Both", left: 0, right: 1 }); 20 | }); 21 | 22 | Deno.test("These fold", () => { 23 | const fold = T.fold(AS.add, AS.add, (a, b) => a + b); 24 | assertEquals(fold(T.left(1)), 2); 25 | assertEquals(fold(T.right(2)), 3); 26 | assertEquals(fold(T.both(2, 2)), 4); 27 | }); 28 | 29 | Deno.test("These isLeft", () => { 30 | assertEquals(T.isLeft(T.left(1)), true); 31 | assertEquals(T.isLeft(T.right(1)), false); 32 | assertEquals(T.isLeft(T.both(1, 1)), false); 33 | }); 34 | 35 | Deno.test("These isRight", () => { 36 | assertEquals(T.isRight(T.left(1)), false); 37 | assertEquals(T.isRight(T.right(1)), true); 38 | assertEquals(T.isRight(T.both(1, 1)), false); 39 | }); 40 | 41 | Deno.test("These isBoth", () => { 42 | assertEquals(T.isBoth(T.left(1)), false); 43 | assertEquals(T.isBoth(T.right(1)), false); 44 | assertEquals(T.isBoth(T.both(1, 1)), true); 45 | }); 46 | 47 | Deno.test("These getShow", () => { 48 | const f = { show: (n: number) => n.toString() }; 49 | const { show } = T.getShow(f, f); 50 | assertEquals(show(T.left(1)), "Left(1)"); 51 | assertEquals(show(T.right(1)), "Right(1)"); 52 | assertEquals(show(T.both(1, 1)), "Both(1, 1)"); 53 | }); 54 | 55 | Deno.test("These getSemigroup", () => { 56 | const Semigroup = T.getSemigroup(semigroupSum, semigroupSum); 57 | const concat = Semigroup.concat; 58 | const cl = concat(T.left(1)); 59 | const cr = concat(T.right(1)); 60 | const cb = concat(T.both(1, 1)); 61 | assertEquals(cl(T.left(1)), T.left(2)); 62 | assertEquals(cl(T.right(1)), T.both(1, 1)); 63 | assertEquals(cl(T.both(1, 1)), T.both(2, 1)); 64 | assertEquals(cr(T.left(1)), T.both(1, 1)); 65 | assertEquals(cr(T.right(1)), T.right(2)); 66 | assertEquals(cr(T.both(1, 1)), T.both(1, 2)); 67 | assertEquals(cb(T.left(1)), T.both(2, 1)); 68 | assertEquals(cb(T.right(1)), T.both(1, 2)); 69 | assertEquals(cb(T.both(1, 1)), T.both(2, 2)); 70 | }); 71 | 72 | Deno.test("These Functor", () => { 73 | AS.assertFunctor(T.Functor, { 74 | ta: T.right(1), 75 | fai: AS.add, 76 | fij: AS.multiply, 77 | }); 78 | }); 79 | 80 | Deno.test("These Bifunctor", () => { 81 | AS.assertBifunctor(T.Bifunctor, { 82 | tab: T.both(1, 1), 83 | fai: AS.add, 84 | fij: AS.multiply, 85 | fbx: AS.multiply, 86 | fxy: AS.add, 87 | }); 88 | }); 89 | 90 | Deno.test("These Foldable", () => { 91 | AS.assertFoldable(T.Foldable, { 92 | a: 0, 93 | tb: T.right(1), 94 | faia: (a: number, i: number) => a + i, 95 | }); 96 | }); 97 | 98 | Deno.test("These bimap", () => { 99 | const bimap = T.bimap(AS.add, AS.add); 100 | assertEquals(bimap(T.left(1)), T.left(2)); 101 | assertEquals(bimap(T.right(1)), T.right(2)); 102 | assertEquals(bimap(T.both(1, 1)), T.both(2, 2)); 103 | }); 104 | 105 | Deno.test("These mapLeft", () => { 106 | const mapLeft = T.mapLeft(AS.add); 107 | assertEquals(mapLeft(T.left(1)), T.left(2)); 108 | assertEquals(mapLeft(T.right(1)), T.right(1)); 109 | assertEquals(mapLeft(T.both(1, 1)), T.both(2, 1)); 110 | }); 111 | 112 | Deno.test("These map", () => { 113 | const map = T.map(AS.add); 114 | assertEquals(map(T.left(1)), T.left(1)); 115 | assertEquals(map(T.right(1)), T.right(2)); 116 | assertEquals(map(T.both(1, 1)), T.both(1, 2)); 117 | }); 118 | 119 | Deno.test("These reduce", () => { 120 | const reduce = T.reduce((n: number, m: number) => n + m, 0); 121 | assertEquals(reduce(T.left(1)), 0); 122 | assertEquals(reduce(T.right(1)), 1); 123 | assertEquals(reduce(T.both(1, 1)), 1); 124 | }); 125 | 126 | Deno.test("These traverse", () => { 127 | const t1 = T.traverse(O.Applicative); 128 | const t2 = t1((n: number) => n === 0 ? O.none : O.some(n)); 129 | assertEquals(t2(T.left(1)), O.some(T.left(1))); 130 | assertEquals(t2(T.right(0)), O.none); 131 | assertEquals(t2(T.right(1)), O.some(T.right(1))); 132 | assertEquals(t2(T.both(1, 0)), O.none); 133 | assertEquals(t2(T.both(1, 1)), O.some(T.both(1, 1))); 134 | }); 135 | -------------------------------------------------------------------------------- /testing/tree.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; 2 | 3 | import * as T from "../tree.ts"; 4 | import * as O from "../option.ts"; 5 | import { _, pipe } from "../fns.ts"; 6 | 7 | import * as AS from "./assert.ts"; 8 | 9 | Deno.test("Tree make", () => { 10 | assertEquals(T.make(1), { value: 1, forest: [] }); 11 | assertEquals(T.make(1, [T.make(2)]), { 12 | value: 1, 13 | forest: [{ value: 2, forest: [] }], 14 | }); 15 | }); 16 | 17 | Deno.test("Tree Functor", () => { 18 | AS.assertFunctor(T.Functor, { 19 | ta: T.make(1, [T.make(2)]), 20 | fai: AS.add, 21 | fij: AS.multiply, 22 | }); 23 | }); 24 | 25 | Deno.test("Tree Apply", () => { 26 | AS.assertApply(T.Apply, { 27 | ta: T.make(1, [T.make(2)]), 28 | fai: AS.add, 29 | fij: AS.multiply, 30 | tfai: T.make(AS.add, [T.make(AS.multiply)]), 31 | tfij: T.make(AS.multiply, [T.make(AS.add)]), 32 | }); 33 | }); 34 | 35 | Deno.test("Tree Applcative", () => { 36 | AS.assertApplicative(T.Applicative, { 37 | a: 1, 38 | ta: T.make(1, [T.make(2)]), 39 | fai: AS.add, 40 | fij: AS.multiply, 41 | tfai: T.make(AS.add, [T.make(AS.multiply)]), 42 | tfij: T.make(AS.multiply, [T.make(AS.add)]), 43 | }); 44 | }); 45 | 46 | Deno.test("Tree Chain", () => { 47 | AS.assertChain(T.Chain, { 48 | a: 1, 49 | ta: T.make(1, [T.make(2)]), 50 | fai: AS.add, 51 | fij: AS.multiply, 52 | tfai: T.make(AS.add, [T.make(AS.multiply)]), 53 | tfij: T.make(AS.multiply, [T.make(AS.add)]), 54 | fati: (n: number) => T.make(n + 1), 55 | fitj: (n: number) => T.make(n * 2, [T.make(n)]), 56 | }); 57 | }); 58 | 59 | Deno.test("Tree Monad", () => { 60 | AS.assertMonad(T.Monad, { 61 | a: 1, 62 | ta: T.make(1, [T.make(2)]), 63 | fai: AS.add, 64 | fij: AS.multiply, 65 | tfai: T.make(AS.add, [T.make(AS.multiply)]), 66 | tfij: T.make(AS.multiply, [T.make(AS.add)]), 67 | fati: (n: number) => T.make(n + 1), 68 | fitj: (n: number) => T.make(n * 2, [T.make(n)]), 69 | }); 70 | }); 71 | 72 | Deno.test("Tree getShow", () => { 73 | const { show } = T.getShow({ show: (n: number) => n.toString() }); 74 | assertEquals(show(T.make(1)), "Tree(1)"); 75 | assertEquals(show(T.make(1, [T.make(2)])), "Tree(1, [Tree(2)])"); 76 | }); 77 | 78 | Deno.test("Tree of", () => { 79 | assertEquals(T.of(1), T.make(1)); 80 | }); 81 | 82 | Deno.test("Tree ap", () => { 83 | assertEquals(pipe(T.make(1), T.ap(T.make(AS.add))), T.make(2)); 84 | assertEquals( 85 | pipe(T.make(1), T.ap(T.make(AS.add, [T.make(AS.add)]))), 86 | T.make(2, [T.make(2)]), 87 | ); 88 | }); 89 | 90 | Deno.test("Tree map", () => { 91 | assertEquals(pipe(T.make(1), T.map(AS.add)), T.make(2)); 92 | }); 93 | 94 | Deno.test("Tree join", () => { 95 | assertEquals(T.join(T.make(T.make(1))), T.make(1)); 96 | }); 97 | 98 | Deno.test("Tree chain", () => { 99 | const chain = T.chain((n: number) => 100 | n === 0 ? T.make(0) : T.make(n, [T.make(1)]) 101 | ); 102 | assertEquals(chain(T.make(2)), T.make(2, [T.make(1)])); 103 | assertEquals(chain(T.make(0)), T.make(0)); 104 | }); 105 | 106 | Deno.test("Tree reduce", () => { 107 | const reduce = T.reduce((n: number, i: number) => n + i, 0); 108 | assertEquals(reduce(T.make(1)), 1); 109 | assertEquals(reduce(T.make(1, [T.make(2)])), 3); 110 | }); 111 | 112 | Deno.test("Tree traverse", () => { 113 | const t1 = T.traverse(O.Applicative); 114 | const t2 = t1((n: number) => n === 0 ? O.none : O.some(n)); 115 | assertEquals(t2(T.make(0)), O.none); 116 | assertEquals(t2(T.make(1)), O.some(T.make(1))); 117 | assertEquals(t2(T.make(1, [T.make(0)])), O.none); 118 | assertEquals(t2(T.make(1, [T.make(2)])), O.some(T.make(1, [T.make(2)]))); 119 | }); 120 | 121 | Deno.test("Tree drawForest", () => { 122 | const ta = T.make(1, [T.make(2), T.make(3)]); 123 | const tb = pipe(ta, T.map((n) => n.toString())); 124 | assertEquals(T.drawForest(tb.forest), "\n├─ 2\n└─ 3"); 125 | }); 126 | 127 | Deno.test("Tree drawTree", () => { 128 | const ta = T.make(1, [T.make(2), T.make(3)]); 129 | const tb = pipe(ta, T.map((n) => n.toString())); 130 | assertEquals(T.drawTree(tb), "1\n├─ 2\n└─ 3"); 131 | }); 132 | 133 | Deno.test("Tree fold", () => { 134 | const fold = T.fold((a: number, bs: number[]) => 135 | bs.reduce((n: number, m: number) => n + m, a) 136 | ); 137 | assertEquals(fold(T.make(1)), 1); 138 | assertEquals(fold(T.make(1, [T.make(2, [T.make(3)])])), 6); 139 | }); 140 | 141 | Deno.test("Tree Do, bind, bindTo", () => { 142 | assertEquals( 143 | pipe( 144 | T.Do(), 145 | T.bind("one", () => T.make(1)), 146 | T.bind("two", ({ one }) => T.make(one + one)), 147 | T.map(({ one, two }) => one + two), 148 | ), 149 | T.make(3), 150 | ); 151 | assertEquals( 152 | pipe( 153 | T.make(1), 154 | T.bindTo("one"), 155 | ), 156 | T.make({ one: 1 }), 157 | ); 158 | }); 159 | -------------------------------------------------------------------------------- /these.ts: -------------------------------------------------------------------------------- 1 | import type * as HKT from "./hkt.ts"; 2 | import type * as TC from "./type_classes.ts"; 3 | 4 | import * as E from "./either.ts"; 5 | import { flow, identity, pipe } from "./fns.ts"; 6 | 7 | /******************************************************************************* 8 | * Types 9 | ******************************************************************************/ 10 | 11 | export type Left = E.Left; 12 | 13 | export type Right = E.Right; 14 | 15 | export type Both = { tag: "Both"; left: L; right: R }; 16 | 17 | export type These = E.Either | Both; 18 | 19 | /******************************************************************************* 20 | * Kind Registration 21 | ******************************************************************************/ 22 | 23 | export const URI = "These"; 24 | 25 | export type URI = typeof URI; 26 | 27 | declare module "./hkt.ts" { 28 | // deno-lint-ignore no-explicit-any 29 | export interface Kinds<_ extends any[]> { 30 | [URI]: These<_[1], _[0]>; 31 | } 32 | } 33 | 34 | /******************************************************************************* 35 | * Constructors 36 | ******************************************************************************/ 37 | 38 | export const left = E.left; 39 | 40 | export const right = E.right; 41 | 42 | export const both = (left: L, right: R): Both => ({ 43 | tag: "Both", 44 | left, 45 | right, 46 | }); 47 | 48 | /******************************************************************************* 49 | * Destructors 50 | ******************************************************************************/ 51 | 52 | export const fold = ( 53 | onLeft: (e: E) => B, 54 | onRight: (a: A) => B, 55 | onBoth: (e: E, a: A) => B, 56 | ) => 57 | (fa: These) => { 58 | switch (fa.tag) { 59 | case "Left": 60 | return onLeft(fa.left); 61 | case "Right": 62 | return onRight(fa.right); 63 | case "Both": 64 | return onBoth(fa.left, fa.right); 65 | } 66 | }; 67 | 68 | /******************************************************************************* 69 | * Guards 70 | ******************************************************************************/ 71 | 72 | export const isLeft = (m: These): m is E.Left => 73 | m.tag === "Left"; 74 | 75 | export const isRight = (m: These): m is E.Right => 76 | m.tag === "Right"; 77 | 78 | export const isBoth = (m: These): m is Both => 79 | m.tag === "Both"; 80 | 81 | /******************************************************************************* 82 | * Module Getters 83 | ******************************************************************************/ 84 | 85 | export const getShow = ( 86 | SE: TC.Show, 87 | SA: TC.Show, 88 | ): TC.Show> => ({ 89 | show: fold( 90 | (left) => `Left(${SE.show(left)})`, 91 | (right) => `Right(${SA.show(right)})`, 92 | (left, right) => `Both(${SE.show(left)}, ${SA.show(right)})`, 93 | ), 94 | }); 95 | 96 | export const getSemigroup = ( 97 | SE: TC.Semigroup, 98 | SA: TC.Semigroup, 99 | ): TC.Semigroup> => ({ 100 | concat: (x) => 101 | (y) => { 102 | if (isLeft(x)) { 103 | if (isLeft(y)) { 104 | return left(SE.concat(x.left)(y.left)); 105 | } else if (isRight(y)) { 106 | return both(x.left, y.right); 107 | } 108 | return both(SE.concat(x.left)(y.left), y.right); 109 | } 110 | 111 | if (isRight(x)) { 112 | if (isLeft(y)) { 113 | return both(y.left, x.right); 114 | } else if (isRight(y)) { 115 | return right(SA.concat(x.right)(y.right)); 116 | } 117 | return both(y.left, SA.concat(x.right)(y.right)); 118 | } 119 | 120 | if (isLeft(y)) { 121 | return both(SE.concat(x.left)(y.left), x.right); 122 | } else if (isRight(y)) { 123 | return both(x.left, SA.concat(x.right)(y.right)); 124 | } 125 | return both(SE.concat(x.left)(y.left), SA.concat(x.right)(y.right)); 126 | }, 127 | }); 128 | 129 | /******************************************************************************* 130 | * Modules 131 | ******************************************************************************/ 132 | 133 | export const Functor: TC.Functor = { 134 | map: (fab) => 135 | (ta) => 136 | isLeft(ta) 137 | ? ta 138 | : isRight(ta) 139 | ? right(fab(ta.right)) 140 | : both(ta.left, fab(ta.right)), 141 | }; 142 | 143 | export const Bifunctor: TC.Bifunctor = { 144 | bimap: (fab, fcd) => 145 | (tac) => 146 | isLeft(tac) 147 | ? left(fab(tac.left)) 148 | : isRight(tac) 149 | ? right(fcd(tac.right)) 150 | : both(fab(tac.left), fcd(tac.right)), 151 | mapLeft: (fef) => (ta) => pipe(ta, Bifunctor.bimap(fef, identity)), 152 | }; 153 | 154 | export const Foldable: TC.Foldable = { 155 | reduce: (faba, a) => 156 | (tb) => 157 | isLeft(tb) ? a : isRight(tb) ? faba(a, tb.right) : faba(a, tb.right), 158 | }; 159 | 160 | export const Traversable: TC.Traversable = { 161 | reduce: Foldable.reduce, 162 | map: Functor.map, 163 | traverse: (A) => 164 | (faub) => 165 | fold( 166 | flow(left, A.of), 167 | flow(faub, A.map((b) => right(b))), 168 | // deno-lint-ignore no-explicit-any 169 | (l, r) => pipe(r, faub, A.map((b) => both(l, b))) as any, 170 | ), 171 | }; 172 | 173 | /******************************************************************************* 174 | * Pipeables 175 | ******************************************************************************/ 176 | 177 | export const { bimap, mapLeft } = Bifunctor; 178 | 179 | export const { map, reduce, traverse } = Traversable; 180 | -------------------------------------------------------------------------------- /tree.ts: -------------------------------------------------------------------------------- 1 | import type * as HKT from "./hkt.ts"; 2 | import type * as TC from "./type_classes.ts"; 3 | 4 | import * as A from "./array.ts"; 5 | import { createDo } from "./derivations.ts"; 6 | import { apply, flow, identity, pipe } from "./fns.ts"; 7 | 8 | /******************************************************************************* 9 | * Types 10 | ******************************************************************************/ 11 | 12 | export type Forest = ReadonlyArray>; 13 | 14 | export type Tree = { 15 | readonly value: A; 16 | readonly forest: Forest; 17 | }; 18 | 19 | /******************************************************************************* 20 | * Kind Registration 21 | ******************************************************************************/ 22 | 23 | export const URI = "Tree"; 24 | 25 | export type URI = typeof URI; 26 | 27 | declare module "./hkt.ts" { 28 | // deno-lint-ignore no-explicit-any 29 | export interface Kinds<_ extends any[]> { 30 | [URI]: Tree<_[0]>; 31 | } 32 | } 33 | 34 | /******************************************************************************* 35 | * Optimizations 36 | ******************************************************************************/ 37 | 38 | // deno-lint-ignore no-explicit-any 39 | const _concat = A.getMonoid>().concat; 40 | 41 | const _draw = (indentation: string, forest: Forest): string => { 42 | let r = ""; 43 | const len = forest.length; 44 | let tree: Tree; 45 | for (let i = 0; i < len; i++) { 46 | tree = forest[i]; 47 | const isLast = i === len - 1; 48 | r += indentation + (isLast ? "└" : "├") + "─ " + tree.value; 49 | r += _draw(indentation + (len > 1 && !isLast ? "│ " : " "), tree.forest); 50 | } 51 | return r; 52 | }; 53 | 54 | const _make = (value: A) => 55 | (forest: Forest): Tree => ({ value, forest }); 56 | 57 | /******************************************************************************* 58 | * Constructors 59 | ******************************************************************************/ 60 | 61 | export const make = (value: A, forest: Forest = A.empty()): Tree => ({ 62 | value, 63 | forest, 64 | }); 65 | 66 | /******************************************************************************* 67 | * Modules 68 | ******************************************************************************/ 69 | 70 | export const Functor: TC.Functor = { 71 | map: (fab) => 72 | (ta) => ({ 73 | value: fab(ta.value), 74 | forest: ta.forest.map(Functor.map(fab)), 75 | }), 76 | }; 77 | 78 | export const Apply: TC.Apply = { 79 | ap: (tfab) => (ta) => pipe(tfab, Monad.chain(flow(Functor.map, apply(ta)))), 80 | map: Functor.map, 81 | }; 82 | 83 | export const Applicative: TC.Applicative = { 84 | of: make, 85 | ap: Apply.ap, 86 | map: Functor.map, 87 | }; 88 | 89 | export const Chain: TC.Chain = { 90 | ap: Apply.ap, 91 | map: Functor.map, 92 | chain: (fatb) => 93 | (ta) => { 94 | const { value, forest } = fatb(ta.value); 95 | return { 96 | value, 97 | forest: _concat(forest)(ta.forest.map(Monad.chain(fatb))), 98 | }; 99 | }, 100 | }; 101 | 102 | export const Monad: TC.Monad = { 103 | of: make, 104 | ap: Apply.ap, 105 | map: Functor.map, 106 | join: Chain.chain(identity), 107 | chain: Chain.chain, 108 | }; 109 | 110 | export const Traversable: TC.Traversable = { 111 | map: Functor.map, 112 | reduce: (faba, b) => 113 | (ta) => { 114 | let r = faba(b, ta.value); 115 | const len = ta.forest.length; 116 | for (let i = 0; i < len; i++) { 117 | r = Traversable.reduce(faba, r)(ta.forest[i]); 118 | } 119 | return r; 120 | }, 121 | // TODO Clean up this implementation 122 | traverse: (AP) => 123 | (faub) => 124 | (ta) => { 125 | const traverseF = A.traverse(AP); 126 | // deno-lint-ignore no-explicit-any 127 | const out = (f: any) => 128 | // deno-lint-ignore no-explicit-any 129 | (ta: any): any => 130 | pipe( 131 | ta.forest, 132 | // deno-lint-ignore no-explicit-any 133 | traverseF(out(f)) as any, 134 | AP.ap(pipe(f(ta.value), AP.map(_make))), 135 | ); 136 | return out(faub)(ta); 137 | }, 138 | }; 139 | 140 | /******************************************************************************* 141 | * Module Getters 142 | ******************************************************************************/ 143 | 144 | export const getShow = (S: TC.Show): TC.Show> => { 145 | const show = (ta: Tree): string => 146 | ta.forest === A.zero || ta.forest.length === 0 147 | ? `Tree(${S.show(ta.value)})` 148 | : `Tree(${S.show(ta.value)}, [${ta.forest.map(show).join(", ")}])`; 149 | return ({ show }); 150 | }; 151 | 152 | /******************************************************************************* 153 | * Pipeables 154 | ******************************************************************************/ 155 | 156 | export const { of, ap, map, join, chain } = Monad; 157 | 158 | export const { reduce, traverse } = Traversable; 159 | 160 | export const drawForest = (forest: Forest): string => 161 | _draw("\n", forest); 162 | 163 | export const drawTree = (tree: Tree): string => 164 | tree.value + drawForest(tree.forest); 165 | 166 | export const fold = (f: (a: A, bs: Array) => B) => 167 | (tree: Tree): B => { 168 | const go = (tree: Tree): B => f(tree.value, tree.forest.map(go)); 169 | return go(tree); 170 | }; 171 | 172 | /******************************************************************************* 173 | * Do Notation 174 | ******************************************************************************/ 175 | 176 | export const { Do, bind, bindTo } = createDo(Monad); 177 | -------------------------------------------------------------------------------- /types.ts: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Fn Type 3 | * 4 | * Represents a function with arbitrary arity of arguments 5 | ******************************************************************************/ 6 | export type Fn = (...as: AS) => B; 7 | 8 | /******************************************************************************* 9 | * UnknownFn Type 10 | * 11 | * Represents a function with unknown unputs and output 12 | ******************************************************************************/ 13 | export type UnknownFn = Fn; 14 | 15 | /******************************************************************************* 16 | * Nil Type 17 | * 18 | * An alias for undefined | null 19 | ******************************************************************************/ 20 | export type Nil = undefined | null; 21 | 22 | /******************************************************************************* 23 | * Lazy Type 24 | * 25 | * An alias type that turns `A` into `() => A` (aka Const) 26 | ******************************************************************************/ 27 | export type Lazy = () => A; 28 | 29 | /******************************************************************************* 30 | * Predicate 31 | * 32 | * An alias for a function that takes type A and returns a boolean 33 | ******************************************************************************/ 34 | export type Predicate = Fn<[A], boolean>; 35 | 36 | /******************************************************************************* 37 | * Guard 38 | * 39 | * An alias for the TypeScript type guard function 40 | ******************************************************************************/ 41 | export type Guard = (a: unknown) => a is A; 42 | 43 | /******************************************************************************* 44 | * Refinement 45 | * 46 | * An alias for a function that takes an A type and refines it to a B type 47 | ******************************************************************************/ 48 | export type Refinement = (a: A) => a is B; 49 | 50 | /******************************************************************************* 51 | * Ordering Type 52 | * 53 | * Ordering is a type alias for -1 | 0 | 1 consts 54 | ******************************************************************************/ 55 | export type Ordering = -1 | 0 | 1; 56 | 57 | /******************************************************************************* 58 | * NonEmptyRecord 59 | * 60 | * NonEmptyRecord returns R only if it has properties, otherwise it 61 | * coerces to never. When used in the argument position this forces a generic 62 | * to have keys. 63 | ******************************************************************************/ 64 | export type NonEmptyRecord = keyof R extends never ? never : R; 65 | 66 | /******************************************************************************* 67 | * Return 68 | * 69 | * Extracts the return type of a function type 70 | ******************************************************************************/ 71 | // deno-lint-ignore no-explicit-any 72 | export type Return = T extends (...as: any[]) => infer R ? R : never; 73 | 74 | /******************************************************************************* 75 | * Args 76 | * 77 | * Extracts the argument types of a function type 78 | ******************************************************************************/ 79 | // deno-lint-ignore no-explicit-any 80 | export type Args = T extends (...as: infer AS) => any ? AS : never; 81 | 82 | /******************************************************************************* 83 | * Or 84 | * 85 | * Replaces a type A with B if B doesn't extend never; 86 | ******************************************************************************/ 87 | export type Or = B extends never ? A : B; 88 | --------------------------------------------------------------------------------