-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement Task as an applicative and update the README
- Loading branch information
1 parent
8a73868
commit 9779caa
Showing
3 changed files
with
198 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
# Functional | ||
|
||
[![deno doc](https://doc.deno.land/badge.svg)](https://doc.deno.land/https/deno.land/x/functional/SumType.js) | ||
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/cli_badges) | ||
[![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/release/sebastienfilion/functional.svg)](https://github.com/sebastienfilion/functional/releases) | ||
|
@@ -9,14 +10,53 @@ | |
* [Maybe](#maybe-type) | ||
* [Either](#either-type) | ||
* [IO](#io-type) | ||
* [Task](#task-type) | ||
* [TypeScript](#typescript) | ||
|
||
# Usage | ||
|
||
This example uses the Ramda library - for simplification - but you should be able to use any library that implements | ||
the [Fantasy-land specifications](https://github.com/fantasyland/fantasy-land). | ||
|
||
```js | ||
import { compose, converge, lift, map, prop } from "https://x.nest.land/[email protected]/source/index.js"; | ||
import Either from "https://deno.land/x/[email protected]/Either.js" | ||
import Task from "https://deno.land/x/[email protected]/Task.js" | ||
|
||
const fetchUser = userID => Task.wrap(_ => fetch(`${URL}/users/${userID}`).then(response => response.json())); | ||
|
||
const sayHello = compose( | ||
converge( | ||
lift((username, email) => `Hello ${username} (${email})!`), | ||
[ | ||
map(prop("username")), | ||
map(prop("email")) | ||
] | ||
), | ||
fetchUser | ||
); | ||
|
||
// Calling `sayHello` results in an instance of `Task` keeping the function pure. | ||
assert(Task.is(sayHello(userID))); | ||
|
||
// Finally, calling `Task#run` will call `fetch` and return a promise | ||
sayHello(userID).run() | ||
.then(container => { | ||
// The returned value should be an instance of `Either.Right` or `Either.Left` | ||
assert(Either.Right.is(container)); | ||
// Forcing to coerce the container to string will show that the final value is our message. | ||
assert(container.toString(), `Either.Right("Hello johndoe ([email protected])!")`); | ||
}); | ||
|
||
// sayHello(userID).run() === Either.Right("Hello johndoe ([email protected])!") | ||
``` | ||
|
||
## Type factory | ||
|
||
The Type factory can be used to build complex data structure. | ||
|
||
```js | ||
import { factorizeType } from "https://deno.land/x/functional/SumType.js" | ||
import { factorizeType } from "https://deno.land/x/functional@v0.5.0/SumType.js" | ||
|
||
const Coordinates = factorizeType("Coordinates", [ "x", "y" ]); | ||
const vector = Coordinates(150, 200); | ||
|
@@ -80,7 +120,7 @@ vector.toString(); | |
## Type Sum factory | ||
|
||
```js | ||
import { factorizeSumType } from "https://deno.land/x/functional/SumType.js" | ||
import { factorizeSumType } from "https://deno.land/x/functional@v0.5.0/SumType.js" | ||
|
||
const Shape = factorizeSumType( | ||
"Shape", | ||
|
@@ -162,7 +202,7 @@ oval.toString(); | |
### Example of writing a binary tree with Sum Types | ||
|
||
```js | ||
import { factorizeSumType } from "https://deno.land/x/functional/SumType.js" | ||
import { factorizeSumType } from "https://deno.land/x/functional@v0.5.0/SumType.js" | ||
|
||
const BinaryTree = factorizeSumType('BinaryTree', { | ||
Node: ['left', 'x', 'right'], | ||
|
@@ -215,7 +255,7 @@ const tree = | |
The `Maybe` type represents potentially `Just` a value or `Nothing`. | ||
|
||
```js | ||
import Maybe from "https://deno.land/x/functional/Maybe.js" | ||
import Maybe from "https://deno.land/x/functional@v0.5.0/Maybe.js" | ||
|
||
const container = Maybe.Just(42); | ||
|
||
|
@@ -240,7 +280,7 @@ This implementation of Maybe is a valid [`Filterable`](https://github.com/fantas | |
The `Either` type represents the possibility of two values; either an `a` or a `b`. | ||
|
||
```js | ||
import Either from "https://deno.land/x/functional/Either.js" | ||
import Either from "https://deno.land/x/functional@v0.5.0/Either.js" | ||
|
||
const container = Either.Right(42); | ||
|
||
|
@@ -263,7 +303,7 @@ This implementation of Maybe is a valid [`Functor`](https://github.com/fantasyla | |
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/IO.js" | ||
import IO from "https://deno.land/x/functional@v0.5.0/IO.js" | ||
|
||
// Eventually 42 | ||
const container = IO(_ => Promise.resolve(42)); | ||
|
@@ -285,14 +325,73 @@ This implementation of IO is a valid [`Functor`](https://github.com/fantasyland/ | |
[`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/[email protected]/Either.js"; | ||
import Task from "https://deno.land/x/[email protected]/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/[email protected]/Task.js" | ||
|
||
let count = 0; | ||
const fetchUser = userID => Task.wrap( | ||
_ => ++count && fetch(`${URL}/users/${userID}`).then(response => response.json()) | ||
); | ||
|
||
const user = fetchUser(userID); | ||
const username = user.map(({ username }) => username); | ||
const email = user.map(({ email }) => email); | ||
|
||
// await user.run() === Either.Right({ email: "[email protected]", username: "johndoe" }) | ||
// await username.run() === Either.Right("johndoe") | ||
// await email.run() === Either.Right("[email protected]") | ||
// count === 1 | ||
``` | ||
|
||
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. | ||
|
||
```ts | ||
// @deno-types="https://deno.land/x/functional/SumType.d.ts" | ||
import { factorizeType, factorizeSumType } from "https://deno.land/x/functional/SumType.js"; | ||
// @deno-types="https://deno.land/x/functional@v0.5.0/SumType.d.ts" | ||
import { factorizeType, factorizeSumType } from "https://deno.land/x/functional@v0.5.0/SumType.js"; | ||
``` | ||
|
||
## Deno | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters