// @flow
import type { HKT2 } from './HKT'
import type { Monad } from './Monad'
import type { Eff } from './Eff'
import type { Either } from './Either'
import type { Monoid } from './Monoid'
import { HKT } from './HKT'
import * as eff from './Eff'
import * as either from './Either'
class IsTask {}
export type TaskV = () => Promise;
// A Task is a computation which yields an effect E and a value A
export type Task = HKT2;
export type PureTask = Task<{}, A>;
export type TaskF = HKT;
export function inj(a: TaskV): Task {
return ((a: any): Task)
}
export function prj(fa: Task): TaskV {
return ((fa: any): TaskV)
}
export function runTask(fut: Task): Eff> {
return eff.inj(prj(fut))
}
export function unsafePerformTask(fut: Task): Promise {
return prj(fut)()
}
export function map(f: (a: A) => B, fa: Task): Task {
return inj(() => unsafePerformTask(fa).then(f))
}
// the derived implementation
// return chain(f => map(f, fa), fab)
// will not execute the futures in parallel
export function ap(fab: Task B>, fa: Task): Task {
return inj(() => {
return Promise.all([
unsafePerformTask(fab),
unsafePerformTask(fa)
]).then(([f, a]) => f(a))
})
}
export function of(a: A): Task {
return inj(() => Promise.resolve(a))
}
export function chain(f: (a: A) => Task, fa: Task): Task {
return inj(() => unsafePerformTask(fa).then(a => unsafePerformTask(f(a))))
}
const noop: () => void = () => {}
// Returns a Task that will never resolve
export function empty(): Task {
return inj(() => new Promise(noop))
}
// Selects the earlier of the two futures
export function concat(x: Task, y: Task): Task {
return inj(() => new Promise((resolve, reject) => {
let done = false
const guard = f => a => {
if (!done) {
done = true
f(a)
}
}
unsafePerformTask(x).then(guard(resolve), guard(reject))
unsafePerformTask(y).then(guard(resolve), guard(reject))
}))
}
// Returns a Task which yields `a` after `delay` milliseconds
export function after(delay: number, a: A): Task {
return inj(() => new Promise(resolve => {
setTimeout(() => {
resolve(a)
}, delay)
}))
}
// Catches a possible error returning it as an Either
export function tryTask(fa: Task): Task> {
return inj(() => unsafePerformTask(fa).then(
either.right,
either.left
))
}
if (false) { // eslint-disable-line
({
map,
ap,
of,
chain,
empty,
concat
}: Monad &
Monoid>)
}