diff --git a/.github/workflows/update-readme.yml b/.github/workflows/update-readme.yml index eb51aee..9bf197f 100644 --- a/.github/workflows/update-readme.yml +++ b/.github/workflows/update-readme.yml @@ -1,9 +1,6 @@ name: Update Readme -on: - create: - tags: - - v* +on: [workflow_dispatch] jobs: update-readme: diff --git a/README.md b/README.md index 9d4bd3e..edc0c22 100644 --- a/README.md +++ b/README.md @@ -2,17 +2,18 @@ Common Functional Programming Algebraic data types for JavaScript that is compatible with most modern browsers and Deno. -[![deno land](http://img.shields.io/badge/available%20on-deno.land/x-lightgrey.svg?logo=deno&labelColor=black)](https://deno.land/x/functional@v0.5.4) +[![deno land](http://img.shields.io/badge/available%20on-deno.land/x-lightgrey.svg?logo=deno&labelColor=black)](https://deno.land/x/functional@v1.0.0) [![deno version](https://img.shields.io/badge/deno-^1.3.2-lightgrey?logo=deno)](https://github.com/denoland/deno) [![GitHub release](https://img.shields.io/github/v/release/sebastienfilion/functional)](https://github.com/sebastienfilion/functional/releases) [![GitHub licence](https://img.shields.io/github/license/sebastienfilion/functional)](https://github.com/sebastienfilion/functional/blob/v0.5.0/LICENSE) [![nest badge](https://nest.land/badge.svg)](https://nest.land/package/functional) - * [Type factory](#type-factory) * [Maybe](#maybe-type) * [Either](#either-type) * [IO](#io-type) * [Task](#task-type) + * [Type factory](#type-factory) + * [Sum Type factory](#sum-type-factory) * [TypeScript](#typescript) # Usage @@ -22,8 +23,8 @@ the [Fantasy-land specifications](https://github.com/fantasyland/fantasy-land). ```js import { compose, converge, lift, map, prop } from "https://x.nest.land/ramda@0.27.0/source/index.js"; -import Either from "https://deno.land/x/functional@v0.5.4/library/Either.js"; -import Task from "https://deno.land/x/functional@v0.5.4/library/Task.js"; +import Either from "https://deno.land/x/functional@v1.0.0/library/Either.js"; +import Task from "https://deno.land/x/functional@v1.0.0/library/Task.js"; const fetchUser = userID => Task.wrap(_ => fetch(`${URL}/users/${userID}`).then(response => response.json())); @@ -59,7 +60,7 @@ As a convenience, when using Functional in the browser, you can use the **unmini ```js import { compose, converge, lift, map, prop } from "https://x.nest.land/ramda@0.27.0/source/index.js"; -import { Either, Task } from "https://deno.land/x/functional@v0.5.4/functional.js"; +import { Either, Task } from "https://deno.land/x/functional@v1.0.0/functional.js"; const fetchUser = userID => Task.wrap(_ => fetch(`${URL}/users/${userID}`).then(response => response.json())); @@ -75,12 +76,134 @@ const sayHello = compose( ); ``` +## `Maybe` type + +The `Maybe` is the most common sum type; it represents the possibility of a value being `null` or `undefined`. + +The `Maybe` type implements the following algebras: + - [x] Alternative + - [x] Comonad + - [x] Monad + +### Example + +```js +const containerA = Maybe.Just(42).map(x => x + 2); +const containerB = Maybe.Nothing.map(x => x + 2); + +assert(Maybe.Just.is(containerA)); +assert(containerA.extract() === 44); +assert(Maybe.Nothing.is(containerB)); +``` + +## `Either` type + +The `Either` is a sum type similar to `Maybe`, but it differs in that a value can be of two possible types +(Left or Right). Commonly the Left type represents an error. + +The `Either` type implements the following algebras: + - [x] Alternative + - [x] Comonad + - [x] Monad + +### Example + +```js +const containerA = Either.Right(42).map(x => x + 2); +const containerB = Either.Left(new Error("The value is not 42.")).map(x => x + 2); +const containerC = containerB.alt(containerA); + +assert(Either.Right.is(containerA)); +assert(containerA.extract() === 44); +assert(Either.Left.is(containerB)); +assert(Either.Right(containerC)); +``` + +## `IO` type + +The `IO` type represents a call to IO. Any Functional Programming purist would tell you that your functions has +to be pure... But in the real world, this is not very useful. Wrapping your call to IO with `IO` will enable you +to postpone the side-effect and keep your program (somewhat) pure. + +The `IO` type implements the following algebras: + - [x] Monad + +### Example + +```js +const container = IO(_ => readFile(`${Deno.cwd()}/dump/hoge`)) + .map(promise => promise.then(text => text.split("\n"))); +// File isn't being read yet. Still pure. + +assert(IO.is(containerA)); + +const promise = container.run(); +// Now, the file is being read. + +const lines = await promise; +``` + +## `Task` type + +The `Task` type is similar in concept to `IO`; it helps keep your function pure when you are working with `IO`. +The biggest difference with `IO` is that this type considers Promise as first-class citizen. Also, it always resolves +to an instance of `Either`; `Either.Right` for a success, `Either.Left` for a failure. + +The `IO` type implements the following algebras: + - [x] Monad + +### Example + +```js +const containerA = Task(_ => readFile(`${Deno.cwd()}/dump/hoge`)) + .map(text => text.split("\n")); +// File isn't being read yet. Still pure. + +assert(Task.is(containerA)); + +const containerB = await container.run(); +// Now, the file is being read. + +assert(Either.Right.is(containerB)); +// The call was successful! + +const lines = containerB.extract(); +``` + +The `Task` factory comes with a special utility method called `wrap`. The result of any function called with `wrap` +will be memoized allowing for safe "logic-forks". + +Take the following example; `containerD` contains the raw text, `containerE` contains the text into lines and +`containerF` contains the lines in inverted order. Because `run` was called thrice, the file was read thrice. 😐 + +```js +let count = 0; +const containerA = Task(_ => ++count && readFile(`${Deno.cwd()}/dump/hoge`)); +const containerB = containerA.map(text => text.split("\n")); +const containerC = containerB.map(lines => text.reverse()); + +assert(Task.is(containerA)); +assert(Task.is(containerB)); +assert(Task.is(containerC)); + +const containerD = await containerA.run(); +const containerE = await containerB.run(); +const containerF = await containerC.run(); + +assert(count === 3); +``` + +Definitely not what we want... Simply wrap the function and bim bam boom - memoization magic! (The file will only be +read once) 🤩 + +Please check-out [Functional IO](https://github.com/sebastienfilion/functional-deno-io) for more practical examples. + ## Type factory The Type factory can be used to build complex data structure. ```js -import { factorizeType } from "https://deno.land/x/functional@v0.5.4/library/SumType.js"; +import { factorizeType } from "https://deno.land/x/functional@v1.0.0/library/factories.js"; const Coordinates = factorizeType("Coordinates", [ "x", "y" ]); const vector = Coordinates(150, 200); @@ -141,10 +264,10 @@ vector.toString(); // "Coordinates(150, 200)" ``` -## Type Sum factory +## Sum Type factory ```js -import { factorizeSumType } from "https://deno.land/x/functional@v0.5.4/library/SumType.js"; +import { factorizeSumType } from "https://deno.land/x/functional@v1.0.0/library/factories.js"; const Shape = factorizeSumType( "Shape", @@ -223,10 +346,10 @@ oval.toString(); // "Shape.Circle(Coordinates(150, 200), 200)" ``` -### Example of writing a binary tree with Sum Types +#### Example of writing a binary tree with Sum Types ```js -import { factorizeSumType } from "https://deno.land/x/functional@v0.5.4/library/SumType.js"; +import { factorizeSumType } from "https://deno.land/x/functional@v1.0.0/library/factories.js"; const BinaryTree = factorizeSumType('BinaryTree', { Node: ['left', 'x', 'right'], @@ -274,151 +397,19 @@ const tree = // tree.reduce((x, y) => x + y, 0) === 15 ``` -## `Maybe` type - -The `Maybe` type represents potentially `Just` a value or `Nothing`. - -```js -import Maybe from "https://deno.land/x/functional@v0.5.4/library/Maybe.js"; - -const container = Maybe.Just(42); - -const serialize = (container) => - container.fold({ - Nothing: () => "There is no value.", - Just: value => `The value is ${value}.` - }); - -// serialize(container) === "The value is 42." -``` - -This implementation of Maybe is a valid [`Filterable`](https://github.com/fantasyland/fantasy-land#filterable), -[`Functor`](https://github.com/fantasyland/fantasy-land#functor), -[`Applicative`](https://github.com/fantasyland/fantasy-land#applicative), -[`Alternative`](https://github.com/fantasyland/fantasy-land#alternative), -[`Foldable`](https://github.com/fantasyland/fantasy-land#foldable), -[`Traversable`](https://github.com/fantasyland/fantasy-land#traversable) and -[`Monad`](https://github.com/fantasyland/fantasy-land#monad). - -## `Either` type - -The `Either` type represents the possibility of two values; either an `a` or a `b`. - -```js -import Either from "https://deno.land/x/functional@v0.5.4/library/Either.js"; - -const container = Either.Right(42); - -const serialize = (container) => - container.fold({ - Left: value => `An error occured: ${value}.`, - Right: value => `The value is ${value}.` - }); - -// serialize(container) === "The value is 42." -``` - -This implementation of Either is a valid [`Functor`](https://github.com/fantasyland/fantasy-land#functor), -[`Applicative`](https://github.com/fantasyland/fantasy-land#applicative), -[`Alternative`](https://github.com/fantasyland/fantasy-land#alternative), -[`Foldable`](https://github.com/fantasyland/fantasy-land#foldable), -[`Traversable`](https://github.com/fantasyland/fantasy-land#traversable) and -[`Monad`](https://github.com/fantasyland/fantasy-land#monad). - -## `IO` type - -The `IO` type represents a function that access IO. It will be lazily executed when the `#run` method is called. - -```js -import IO from "https://deno.land/x/functional@v0.5.4/library/IO.js"; - -// Eventually 42 -const container = IO(_ => Promise.resolve(42)); - -const multiply = container.map(promise => promise.then(x => x * x)); -const add = container.map(promise => promise.then(x => x + x)); - -// multiply === IO(Function) -// add === IO(Function) - -const multiplyThenAdd = multiply.map(promise => promise.then(x => x + x)); - -// multiply.run() === Promise(1764) -// add.run() === Promise(84) -// multiplyThenAdd.run() === Promise(3528) -``` - -This implementation of IO is a valid [`Functor`](https://github.com/fantasyland/fantasy-land#functor), -[`Applicative`](https://github.com/fantasyland/fantasy-land#applicative) and -[`Monad`](https://github.com/fantasyland/fantasy-land#monad). - -## `Task` type - -The `Task` type represents a function that access IO. It will be lazily executed when the `#run` method is called. -Unlike IO, the Task type also abstract away the promise making for a more intuitive experience. -Note that the function must return an instance of [`Either`](#either-type); `Either.Right` to represent a success and -`Either.Left` to represent a failure. Also check-out the [`Task.wrap`](#task-wrap) method. - -If the runtime throws an error, the final value will be `Either.Left(error)`. - -```js -import Either from "https://deno.land/x/functional@v0.5.4/library/Either.js"; -import Task from "https://deno.land/x/functional@v0.5.4/library/Task.js"; - -// Eventually 42 -const container = Task(_ => Promise.resolve(Either.Right(42))); - -const multiply = container.map(x => x * x); -const add = container.map(x => x + x); - -// multiply === Task(Function) -// add === Task(Function) - -const multiplyThenAdd = multiply.map(x => x + x); - -// await multiply.run() === Either.Right(1764) -// await add.run() === Either.Right(84) -// await multiplyThenAdd.run() === Either.Right(3528) -``` - -### `Task.wrap` - -Create a wrapped instance of Task. An instance of `Task` made using the `wrap` method is different in two ways: - - 1. The result of the function call is memoized; - 2. If the function call was successful, the value will automatically be an instance of `Either.Right`; - -```js -import Task from "https://deno.land/x/functional@v0.5.4/library/Task.js"; - -let count = 0; -const fetchUser = userID => Task.wrap( - _ => ++count && fetch(`${URL}/users/${userID}`).then(response => response.json()) -); +## TypeScript -const user = fetchUser(userID); -const username = user.map(({ username }) => username); -const email = user.map(({ email }) => email); +You can import any types or the factories through `mod.ts`. -// await user.run() === Either.Right({ email: "johndoe@gmail.com", username: "johndoe" }) -// await username.run() === Either.Right("johndoe") -// await email.run() === Either.Right("johndoe@gmail.com") -// count === 1 +```ts +import { Either, IO, Maybe, Task, factorizeType, factorySumType } from "https://deno.land/x/functional@v1.0.0/mod.ts"; ``` -This implementation of Task is a valid [`Functor`](https://github.com/fantasyland/fantasy-land#functor), -[`Applicative`](https://github.com/fantasyland/fantasy-land#applicative), -[`Alternative`](https://github.com/fantasyland/fantasy-land#alternative) and -[`Monad`](https://github.com/fantasyland/fantasy-land#monad). - -## TypeScript - -I will try to publish TypeScript type hint files for those who needs it. -So far, I've only implemented the Type factory functions. +Or, you can import individual sub-module with the appropriate TypeScript hint in Deno. ```ts -// @deno-types="https://deno.land/x/functional@v0.5.4/library/SumType.d.ts" -import { factorizeType, factorizeSumType } from "https://deno.land/x/functional@v0.5.4/library/SumType.js"; +// @deno-types="https://deno.land/x/functional@v1.0.0/library/Either.d.ts" +import Either from "https://deno.land/x/functional@v1.0.0/library/Either.js"; ``` ## Deno diff --git a/library/Either.js b/library/Either.js index 21dcb2a..704138a 100644 --- a/library/Either.js +++ b/library/Either.js @@ -1,12 +1,12 @@ -import { factorizeSumType } from "./SumType.js"; +import { factorizeSumType } from "./factories.js"; import { $$value } from "./Symbols.js"; /** * The `Either` is a sum type similar to `Maybe`, but it differs in that a value can be of two possible types - * (Left or Right). Commonly the Left type is used to handle errors. + * (Left or Right). Commonly the Left type represents an error. * - * The `Maybe` type implements the following algebras: + * The `Either` type implements the following algebras: * - [x] Alternative * - [x] Comonad * - [x] Monad diff --git a/library/Either_test.js b/library/Either_test.js index 5840bc3..2169c1d 100644 --- a/library/Either_test.js +++ b/library/Either_test.js @@ -1,6 +1,6 @@ import { assertEquals } from "https://deno.land/std@0.70.0/testing/asserts.ts"; -import { factorizeType } from "./SumType.js"; +import { factorizeType } from "./factories.js"; import Either from "./Either.js"; import { $$value } from "./Symbols.js"; diff --git a/library/IO.d.ts b/library/IO.d.ts index 5624e6a..cbc1bef 100644 --- a/library/IO.d.ts +++ b/library/IO.d.ts @@ -1,19 +1,16 @@ export interface IOPrototype { - ap, K>(container: T): this; - chain, K>(unaryFunction: (value: Z) => T): this; - extend, K>(unaryFunction: (container: T) => K): this; - extract(): Z; - map(unaryFunction: (value: Z) => K): this; - of>(value: Z): this; + ap>(container: T): this; + chain, K>(unaryFunction: (value: K) => T): this; + map(unaryFunction: (value: K) => Y): this; + of(value: K): this; + run(): K; toString(): string; } -declare function IO, Z>(buffer: Uint8Array): T; -declare function IO, Z>(unaryFunction: (buffer: Uint8Array) => Uint8Array): T; +declare function IO, Z>(asyncFunction: () => Z): T; declare namespace IO { - export function empty, Z>(): T; - export function of, Z>(value: Z): T; - export function of, Z, K>(unaryFunction: (value: K) => Z): T; + export function is(container: Z): boolean; + export function of, Z, K>(value: K): T; } export default IO; \ No newline at end of file diff --git a/library/IO.js b/library/IO.js index d1036a5..b5782c9 100644 --- a/library/IO.js +++ b/library/IO.js @@ -1,11 +1,31 @@ -import { factorizeType } from "./SumType.js"; +import { factorizeType } from "./factories.js"; + +/** + * The `IO` type represents a call to IO. Any Functional Programming purist would tell you that your functions has + * to be pure... But in the real world, this is not very useful. Wrapping your call to IO with `IO` will enable you + * to postpone the side-effect and keep your program (somewhat) pure. + * + * The `IO` type implements the following algebras: + * - [x] Monad + * + * ## Example + * + * ```js + * const container = IO(_ => readFile(`${Deno.cwd()}/dump/hoge`)) + * .map(promise => promise.then(text => text.split("\n"))); + * // File isn't being read yet. Still pure. + * + * assert(IO.is(containerA)); + * + * const promise = container.run(); + * // Now, the file is being read. + * + * const lines = await promise; + * ``` + */ export const IO = factorizeType("IO", [ "asyncFunction" ]); -IO.empty = _ => IO(_ => null); - -IO.of = IO.prototype.of = IO.prototype["fantasy-land/of"] = value => IO(_ => value); - IO.prototype.ap = IO.prototype["fantasy-land/ap"] = function (container) { // return IO.is(container) ? IO(_ => container.asyncFunction(this.asyncFunction())) : container @@ -27,4 +47,6 @@ IO.prototype.run = function () { return this.asyncFunction(); }; +IO.of = IO.prototype.of = IO.prototype["fantasy-land/of"] = value => IO(_ => value); + export default IO; diff --git a/library/IO_test.js b/library/IO_test.js index f3bc914..4529209 100644 --- a/library/IO_test.js +++ b/library/IO_test.js @@ -15,6 +15,20 @@ Deno.test( } ); +Deno.test( + "IO: #chain - Associativity", + async () => { + const container = IO(_ => 42); + const f = x => IO(_ => x + 2); + const g = x => IO(_ => x * 2); + + assertEquals( + container.chain(f).chain(g).run(), + container.chain(value => f(value).chain(g)).run() + ); + } +); + Deno.test( "IO: #map - Identity", () => { @@ -40,3 +54,61 @@ Deno.test( ); } ); + +Deno.test( + "IO: #of - Identity (Applicative)", + () => { + const container = IO(_ => 42); + + assertEquals( + container.ap(IO.of(x => x)).run(), + container.run() + ); + } +); + +Deno.test( + "IO: #of - Left identity (Chainable)", + async () => { + const container = IO(_ => 42); + const f = x => IO(_ => x + 2); + + assertEquals( + container.chain(IO.of).chain(f).run(), + container.chain(f).run() + ); + } +); + +Deno.test( + "IO: #of - Right identity (Chainable)", + async () => { + const container = IO(_ => 42); + const f = x => IO(_ => x + 2); + + assertEquals( + container.chain(f).chain(IO.of).run(), + container.chain(f).run() + ); + } +); + +Deno.test( + "IO: #of - Homomorphism", + () => + assertEquals( + IO.of(42).ap(IO.of(x => x + 2)).run(), + IO.of((x => x + 2)(42)).run() + ) +); + +Deno.test( + "IO: #of - Interchange", + () => + assertEquals( + IO.of(42) + .ap(IO(_ => x => x + 2)).run(), + IO(_ => x => x + 2) + .ap(IO(_ => f => f(42))).run() + ) +); diff --git a/library/Maybe.js b/library/Maybe.js index 4e272c0..ed49897 100644 --- a/library/Maybe.js +++ b/library/Maybe.js @@ -1,4 +1,4 @@ -import { factorizeSumType } from "./SumType.js"; +import { factorizeSumType } from "./factories.js"; import { $$value } from "./Symbols.js"; diff --git a/library/Maybe_test.js b/library/Maybe_test.js index d4a77d2..5c09ea7 100644 --- a/library/Maybe_test.js +++ b/library/Maybe_test.js @@ -1,5 +1,5 @@ import { assertEquals } from "https://deno.land/std@0.70.0/testing/asserts.ts"; -import { factorizeType } from "./SumType.js"; +import { factorizeType } from "./factories.js"; import Maybe from "./Maybe.js"; import { $$value } from "./Symbols.js"; diff --git a/library/Task.d.ts b/library/Task.d.ts new file mode 100644 index 0000000..316605e --- /dev/null +++ b/library/Task.d.ts @@ -0,0 +1,18 @@ +export interface TaskPrototype { + ap>(container: T): this; + chain, K>(unaryFunction: (value: K) => T): this; + map(unaryFunction: (value: K) => Y): this; + of(value: K): this; + run(): Promise; + toString(): string; +} + +declare function Task, Z>(asyncFunction: () => Z): T; +declare namespace Task { + export function from, Z>(unaryFunction: Z): T; + export function is(container: Z): boolean; + export function of, Z, K>(value: K): T; + export function wrap, Z>(unaryFunction: Z): T; +} + +export default Task; \ No newline at end of file diff --git a/library/Task.js b/library/Task.js index 38f8a98..415364a 100644 --- a/library/Task.js +++ b/library/Task.js @@ -1,8 +1,63 @@ -import { factorizeType } from "./SumType.js"; +import { factorizeType } from "./factories.js"; import Either from "./Either.js"; import { $$debug, $$inspect } from "./Symbols.js"; +/** + * The `Task` type is similar in concept to `IO`; it helps keep your function pure when you are working with `IO`. + * The biggest difference with `IO` is that this type considers Promise as first-class citizen. Also, it always resolves + * to an instance of `Either`; `Either.Right` for a success, `Either.Left` for a failure. + * + * The `IO` type implements the following algebras: + * - [x] Monad + * + * ## Example + * + * ```js + * const containerA = Task(_ => readFile(`${Deno.cwd()}/dump/hoge`)) + * .map(text => text.split("\n")); + * // File isn't being read yet. Still pure. + * + * assert(Task.is(containerA)); + * + * const containerB = await container.run(); + * // Now, the file is being read. + * + * assert(Either.Right.is(containerB)); + * // The call was successful! + * + * const lines = containerB.extract(); + * ``` + * + * The `Task` factory comes with a special utility method called `wrap`. The result of any function called with `wrap` + * will be memoized allowing for safe "logic-forks". + * + * Take the following example; `containerD` contains the raw text, `containerE` contains the text into lines and + * `containerF` contains the lines in inverted order. Because `run` was called thrice, the file was read thrice. 😐 + * + * ```js + * let count = 0; + * const containerA = Task(_ => ++count && readFile(`${Deno.cwd()}/dump/hoge`)); + * const containerB = containerA.map(text => text.split("\n")); + * const containerC = containerB.map(lines => text.reverse()); + * + * assert(Task.is(containerA)); + * assert(Task.is(containerB)); + * assert(Task.is(containerC)); + * + * const containerD = await containerA.run(); + * const containerE = await containerB.run(); + * const containerF = await containerC.run(); + * + * assert(count === 3); + * ``` + * + * Definitely not what we want... Simply wrap the function and bim bam boom - memoization magic! (The file will only be + * read once) 🤩 + * + * Please check-out [Functional IO](https://github.com/sebastienfilion/functional-deno-io) for more practical examples. + */ + export const Task = factorizeType("Task", [ "asyncFunction" ]); const serializeFunctionForDebug = asyncFunction => @@ -17,8 +72,6 @@ const serializeFunctionForDebug = asyncFunction => .replace(/[\n\r]/g, "") .replace(/\s\s*/g, " ") -Task.from = (composedFunction) => Task(composedFunction); - Task.wrap = asyncFunction => { let promise; const proxyFunction = function (...argumentList) { @@ -46,18 +99,6 @@ Task.wrap = asyncFunction => { ); }; -Task.empty = Task.prototype.empty = Task.prototype["fantasy-land/empty"] = _ => Task(_ => function () {}); - -Task.of = Task.prototype.of = Task.prototype["fantasy-land/of"] = value => - Object.defineProperty( - Task(_ => Promise.resolve(Either.Right(value))), - $$debug, - { - writable: false, - value: `Task(${serializeFunctionForDebug(value)})` - } - ); - Task.prototype.ap = Task.prototype["fantasy-land/ap"] = function (container) { return Object.defineProperty( @@ -190,6 +231,16 @@ Task.prototype.catch = function (unaryFunction) { }); }; +Task.of = Task.prototype.of = Task.prototype["fantasy-land/of"] = value => + Object.defineProperty( + Task(_ => Promise.resolve(Either.Right(value))), + $$debug, + { + writable: false, + value: `Task(${serializeFunctionForDebug(value)})` + } + ); + Task.prototype.run = async function () { const maybePromise = this.asyncFunction(); diff --git a/library/Task_test.js b/library/Task_test.js index 3bb372c..57a9749 100644 --- a/library/Task_test.js +++ b/library/Task_test.js @@ -295,4 +295,4 @@ Deno.test( "Task(_ => Promise.resolve(42)).ap(Task(x => x * 2))" ); } -); \ No newline at end of file +); diff --git a/library/factories.d.ts b/library/factories.d.ts new file mode 100644 index 0000000..a7dbfb9 --- /dev/null +++ b/library/factories.d.ts @@ -0,0 +1,21 @@ +export interface TypeRepresentationPrototype { + from(propertyCollection: Record): T; + is(container: Z): boolean; + toString(): string; +} + +export interface SumTypeRepresentationPrototype { + from(propertyCollection: Record): T; + is(container: Z): boolean; + toString(): string; +} + +export function factorizeType( + typeName: string, + propertyNameList: string[], +): T; + +export function factorizeSumType( + typeName: string, + propertyNameListByTag: Record, +): T; diff --git a/library/SumType.js b/library/factories.js similarity index 100% rename from library/SumType.js rename to library/factories.js diff --git a/library/SumType_test.js b/library/factories_test.js similarity index 97% rename from library/SumType_test.js rename to library/factories_test.js index 857ecf2..b633127 100644 --- a/library/SumType_test.js +++ b/library/factories_test.js @@ -1,6 +1,6 @@ import { assert, assertEquals } from "https://deno.land/std@0.65.0/testing/asserts.ts"; -import { factorizeSumType, factorizeType } from "./SumType.js"; +import { factorizeSumType, factorizeType } from "./factories.js"; Deno.test( "SumType #factorizeType: Tuple", diff --git a/library/types_test.ts b/library/types_test.ts index fcbf6e9..19f8740 100644 --- a/library/types_test.ts +++ b/library/types_test.ts @@ -4,6 +4,10 @@ import type { MaybePrototype } from "./Maybe.d.ts"; // @deno-types="./Either.d.ts" import Either from "./Either.js"; import type { EitherRightPrototype } from "./Either.d.ts"; +// @deno-types="./IO.d.ts" +import IO from "./IO.js"; +// @deno-types="./Task.d.ts" +import Task from "./Task.js"; Deno.test( "Maybe type check", @@ -67,3 +71,44 @@ Deno.test( Either.Right.is(containerN); } ); + +Deno.test( + "IO type check", + () => { + const containerA = IO(() => 42); + const containerB = IO(() => (value: number) => value + 2); + const f = (value: number) => IO(() => value + 2); + const g = (value: number) => value + 2; + + const containerC = containerA.ap(containerB); + const containerD = containerC.chain(f); + const containerE = containerD.map(g); + IO.is(containerE); + containerE.run(); + } +); + +Deno.test( + "Task type check", + () => { + const containerA = Task(() => Promise.resolve(42)); + const containerB = Task.wrap(() => Promise.resolve(42)); + const containerC = Task.of(42); + const containerD = Task.of((value: number) => value + 2); + const f = (value: number) => Task(() => value + 2); + const g = (value: number) => value + 2; + + const containerE = containerA.ap(containerD); + const containerF = containerE.chain(f) + const containerG = containerF.map(g); + const containerH = containerB.map(g); + const containerI = containerC.map(g); + Task.is(containerG); + Task.is(containerH); + Task.is(containerI); + + containerG.run(); + containerH.run(); + containerI.run(); + } +); diff --git a/mod.js b/mod.js index c687906..1ffe1f0 100644 --- a/mod.js +++ b/mod.js @@ -1,6 +1,6 @@ export * from "./library/Either.js"; export * from "./library/IO.js"; export * from "./library/Maybe.js"; -export * from "./library/SumType.js"; +export * from "./library/factories.js"; export * from "./library/Symbols.js" export * from "./library/Task.js"; diff --git a/mod.ts b/mod.ts index d8a5cd7..841cd6f 100644 --- a/mod.ts +++ b/mod.ts @@ -1,8 +1,11 @@ // @deno-types="./library/Either.d.ts" export * from "./library/Either.js"; +// @deno-types="./library/IO.d.ts" export * from "./library/IO.js"; // @deno-types="./library/Maybe.d.ts" export * from "./library/Maybe.js"; -export * from "./library/SumType.js"; +// @deno-types="./library/factories.d.ts" +export * from "./library/factories.js"; export * from "./library/Symbols.js"; +// @deno-types="./library/Task.d.ts" export * from "./library/Task.js";