Skip to content

Instantly share code, notes, and snippets.

@gvergnaud
Last active June 27, 2021 19:34
Show Gist options
  • Save gvergnaud/694d9a65b56dfab66b28e72b2341cbbb to your computer and use it in GitHub Desktop.
Save gvergnaud/694d9a65b56dfab66b28e72b2341cbbb to your computer and use it in GitHub Desktop.
Task.js — Full Implementation (SemiGroup, Monoid, Functor, Monad, Applicative)
import { compose, add, map, range } from 'lodash/fp'
import { Just, Nothing } from './Maybe'
class Task {
constructor(fork) {
this.fork = fork
}
static of(x) {
return Task.resolve(x)
}
static resolve(x) {
return new Task((_, resolve) => resolve(x))
}
static reject(x) {
return new Task((reject, _) => reject(x))
}
static empty() {
return new Task((_, resolve) => resolve())
}
/**
* use the first Task to resolve
* @summary concat :: Task a b -> Task a b -> Task a b
*/
concat(task) {
return new Task((reject, resolve) => {
let done = false
const guard = f => x => {
if (!done) {
done = true
f(x)
}
}
task.fork(guard(reject), guard(resolve))
this.fork(guard(reject), guard(resolve))
})
}
/**
* @summary map :: (a -> b) -> Task Err a -> Task Err b
*/
map(f) {
return new Task((reject, resolve) => this.fork(
reject,
compose(resolve, f)
))
}
/**
* @summary chain :: (a -> Task Err b) -> Task Err a -> Task Err b
*/
chain(f) {
return new Task((reject, resolve) => this.fork(
reject,
x => f(x).fork(reject, resolve)
))
}
/**
* @summary ap :: Task Err (a -> b) -> Task Err a -> Task Err b
*/
ap(task) {
return new Task((reject, resolve) => {
this.fork(
reject,
f => task.fork(reject, compose(resolve, f))
)
})
}
/**
* fold the Rejected or Resolved Task into a Resolved Task by applying a
* different mapper whether its Rejected or Resolved.
* @summary fold :: (a -> b) -> (c -> b) -> Task a c -> Task _ b
*/
fold(f, g) {
return new Task((reject, resolve) => this.fork(
compose(resolve, f),
compose(resolve, g)
))
}
/**
* like fold but using pattern matching
* @summary cata :: { Rejected :: a -> c, Resolved :: b -> c } -> Task a b -> Task _ c
*/
cata({ Rejected, Resolved }) {
return this.fold(Rejected, Resolved)
}
/**
* transform a Rejected to a Resolved and vice versa
* @summary swap :: _ -> Task a b -> Task b a
*/
swap() {
return new Task((reject, resolve) => this.fork(resolve, reject))
}
/**
* apply a mapper to the rejected or resolved Task
* @summary bimap :: (a -> b) -> (c -> d) -> Task a c -> Task b d
*/
bimap(f, g) {
return new Task((reject, resolve) => this.fork(
compose(reject, f),
compose(resolve, g)
))
}
/**
* @summary rejectedMap :: (a -> b) -> Task a x -> Task b x
*/
rejectMap(f) {
return new Task((reject, resolve) => this.fork(
compose(reject, f),
resolve
))
}
/**
* @summary rejectedChain :: (a -> Task b x) -> Task a x -> Task b x
*/
rejectChain(f) {
return new Task((reject, resolve) => this.fork(
x => f(x).compose(reject, f),
resolve
))
}
/**
* @summary toMaybe :: _ -> Task a b -> Task Nothing (Just b)
*/
toMaybe() {
return new Task((reject, resolve) => this.fork(
compose(resolve, Nothing.of),
compose(resolve, Just.of)
))
}
/**
* @summary fromMaybe :: a -> Maybe x -> Task a x
*/
static fromMaybe(defaultValue, maybe) {
return new Task((reject, resolve) => {
cata({
Nothing: () => reject(defaultValue),
Just: x => resolve(x)
})(maybe)
})
}
/**
* @summary toPromise :: Task a b -> Promise b a
*/
toPromise() {
return new Promise((resolve, reject) => this.fork(reject, resolve))
}
/**
* @summary fromPromise :: Promise b a -> Task a b
*/
static fromPromise(promise) {
return new Task((reject, resolve) => promise.then(resolve, reject))
}
}
/* ----------------------------------------- *
Let's use that
* ----------------------------------------- */
const compose = (...fns) => x => fns.reduceRight((acc, f) => f(acc), x)
const map = f => xs => xs.map(f)
const range = (a, b) => a === b
? [b]
: [a, ...range(a + 1, b)]
const add = a => b => a + b
const noop = () => {}
const delay = (duration, value) => new Task((_, resolve) => setTimeout(() => resolve(value), duration))
const getUsers = count => new Task((_, resolve) => {
setTimeout(() => {
resolve(range(0, count).map(x => ({ id: x, username: 'Bob' })))
}, 200)
})
const userComponent = ({ id, username }) => `
<div>
<p>userID: ${id}</p>
<p>user name: ${username}</p>
</div>
`
const div = x => `<div>${[].concat(x).join('')}</div>`
Task
.of(add)
.ap(Task.of(3))
.ap(Task.of(8))
.map(x => x / 10)
.map(add)
.ap(delay(1000, 70))
.map(Math.ceil)
.chain(getUsers)
.map(map(userComponent))
.map(div)
.fork(noop, x => console.log(x))
/*
=> `
<div>
<div>
<p>userID: 1</p>
<p>user name: Bob</p>
</div>
<div>
<p>userID: 2</p>
<p>user name: Bob</p>
</div>
...
</div>
`
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment