diff --git a/docs/modules/IO.ts.md b/docs/modules/IO.ts.md index c34918c69..178239787 100644 --- a/docs/modules/IO.ts.md +++ b/docs/modules/IO.ts.md @@ -184,7 +184,7 @@ Added in v2.0.0 **Signature** ```ts -export const of = (a: A): IO => () => ... +export const of = (a: A): IO => ... ``` Added in v2.0.0 diff --git a/src/IO.ts b/src/IO.ts index f29525a80..69a14f5df 100644 --- a/src/IO.ts +++ b/src/IO.ts @@ -141,17 +141,17 @@ export function getMonoid(M: Monoid): Monoid> { /** * @since 2.0.0 */ -export const of = (a: A): IO => () => a +export const of = (a: A): IO => pureE(a) /** * @since 2.0.0 */ export const io: Monad1 & MonadIO1 = { URI, - map: (ma, f) => () => f(ma()), + map: mapE, of, - ap: (mab, ma) => () => mab()(ma()), - chain: (ma, f) => () => f(ma())(), + ap: applyE, + chain: bindE, fromIO: identity } @@ -187,3 +187,73 @@ export { */ map } + +const PURE = 'PURE' +const MAP = 'MAP' +const APPLY = 'APPLY' +const BIND = 'BIND' +const APPLY_FUNC = 'APPLY_FUNC' + +function pureE(x: any) { + return mkEff(PURE, x) +} + +function mapE(effect: any, f: any) { + return mkEff(MAP, f, effect) +} + +function applyE(effF: any, effect: any) { + return mkEff(APPLY, effect, effF) +} + +function bindE(effect: any, f: any) { + return mkEff(BIND, f, effect) +} + +function mkEff(tag: 'PURE' | 'MAP' | 'APPLY' | 'BIND' | 'APPLY_FUNC', _0: any, _1?: any) { + const effect = function $effect() { + return runEff($effect) + } as any + effect.tag = tag + effect._0 = _0 + effect._1 = _1 + return effect +} + +function runEff(inputEff: any) { + let operations = [] + let effect = inputEff + let res + let op + effLoop: for (;;) { + if (effect.tag !== undefined) { + if (effect.tag === MAP || effect.tag === BIND || effect.tag === APPLY) { + operations.push(effect) + effect = effect._1 + continue + } + // here `tag === PURE` + res = effect._0 + } else { + res = effect() + } + + // tslint:disable-next-line no-conditional-assignment + while ((op = operations.pop())) { + if (op.tag === MAP) { + res = op._0(res) + } else if (op.tag === APPLY_FUNC) { + res = op._0(res) + } else if (op.tag === APPLY) { + effect = op._0 + operations.push({ tag: APPLY_FUNC, _0: res }) + continue effLoop + } else { + // op.tag === BIND + effect = op._0(res) + continue effLoop + } + } + return res + } +} diff --git a/test/IO.ts b/test/IO.ts index e0dfb260e..5d171e90f 100644 --- a/test/IO.ts +++ b/test/IO.ts @@ -2,6 +2,7 @@ import * as assert from 'assert' import { IO, getSemigroup, io, getMonoid } from '../src/IO' import { semigroupSum } from '../src/Semigroup' import { monoidSum } from '../src/Monoid' +import { array, range } from '../src/Array' describe('IO', () => { it('ap', () => { @@ -32,4 +33,8 @@ describe('IO', () => { assert.strictEqual(M.concat(M.empty, append('b'))(), 2) assert.deepStrictEqual(log, ['a', 'b']) }) + + it('stack safe', () => { + array.sequence(io)(range(0, 20000).map(io.of))() + }) }) diff --git a/test/IOEither.ts b/test/IOEither.ts index 5dab9e3e2..2b14bfb78 100644 --- a/test/IOEither.ts +++ b/test/IOEither.ts @@ -6,7 +6,7 @@ import { monoidString } from '../src/Monoid' import { semigroupSum, semigroupString } from '../src/Semigroup' import { none, some } from '../src/Option' import { pipe, pipeable } from '../src/pipeable' -import { getMonoid } from '../src/Array' +import { getMonoid, array, range } from '../src/Array' describe('IOEither', () => { it('fold', () => { @@ -283,4 +283,8 @@ describe('IOEither', () => { assert.deepStrictEqual(r3(), _.left(['a'])()) }) }) + + it('stack safe', () => { + array.sequence(_.ioEither)(range(0, 20000).map(_.ioEither.of))() + }) })