Skip to content

Instantly share code, notes, and snippets.

@wnadurski
Last active January 4, 2023 08:15
Show Gist options
  • Save wnadurski/51a3e8cf06c8fe0cc3dd50b87fd90365 to your computer and use it in GitHub Desktop.
Save wnadurski/51a3e8cf06c8fe0cc3dd50b87fd90365 to your computer and use it in GitHub Desktop.
import * as O from 'fp-ts/Option'
import * as IO from 'fp-ts/IO'
import * as T from 'fp-ts/Task'
import * as A from 'fp-ts/Array'
import { HKT, Kind, URIS } from 'fp-ts/HKT'
import { Monad, Monad1 } from 'fp-ts/Monad'
import { pipe } from 'fp-ts/function'
const _: any = function* (x: any): any {
return yield x
}
const expectType = <Expected>(value: Expected) => {}
export function Do<F extends URIS>(
F: Monad1<F>,
): <Yielded, Returned>(
f: (g: <A>(fa: Kind<F, A>) => Generator<A, A>) => Generator<Yielded, Returned, any>,
) => Kind<F, Returned>
export function Do<F>(
F: Monad<F>,
): <Yielded, Returned>(
f: (g: <A>(fa: HKT<F, A>) => Generator<A, A>) => Generator<Yielded, Returned, any>,
) => HKT<F, Returned>
export function Do<F>(
F: Monad<F>,
): <Yielded, Returned>(
f: (g: <A>(a: HKT<F, A>) => Generator<A, A>) => Generator<Yielded, Returned, any>,
) => HKT<F, Returned> {
return f => {
function run(replayStack: any[]): any {
const iter = f(_)
let state = iter.next()
for (const a of replayStack) {
if (state.done) {
return F.of(state.value)
}
state = iter.next(a)
}
if (state.done) {
return F.of(state.value)
}
return F.chain(state.value as any, (value: any) => {
return run([...replayStack, value])
})
}
return run([])
}
}
it('works for option', () => {
const result = Do(O.Monad)(function* (_) {
const x = yield* _(O.some(2))
const y = yield* _(O.some('asd'))
return y + x.toString()
})
expect(result).toEqual(O.some('asd2'))
expectType<O.Option<string>>(result)
})
it('works for IO', () => {
const someSideEffect = jest.fn()
const result = Do(IO.Monad)(function* (_) {
const x = yield* _(IO.of(2))
yield* _(() => someSideEffect())
return 'asd' + x.toString()
})
expectType<IO.IO<string>>(result)
expect(result()).toEqual('asd2')
expect(someSideEffect).toHaveBeenCalled()
})
it('works for Task', async () => {
const someSideEffect = jest.fn().mockReturnValue(Promise.resolve(undefined))
const result = Do(T.Monad)(function* (_) {
const x = yield* _(T.of(123))
yield* _(someSideEffect)
const y = yield* _(T.of(10))
return x + y
})
expectType<T.Task<number>>(result)
expect(await result()).toEqual(133)
expect(someSideEffect).toHaveBeenCalled()
})
describe('array', () => {
it('makes combination', () => {
const result = Do(A.Monad)(function* (_) {
const a = yield* _([1, 2])
const b = yield* _(['a', 'b'])
return a.toString() + b
})
const result2 = pipe(
A.Do,
A.bind('a', () => [1, 2]),
A.bind('b', () => ['a', 'b']),
A.map(({ a, b }) => a.toString() + b),
)
expectType<Array<string>>(result)
expect(result).toEqual(['1a', '1b', '2a', '2b'])
expect(result).toEqual(result2)
})
it('works', () => {
const result = Do(A.Monad)(function* (_) {
const a = yield* _([1])
const b = yield* _(['a'])
const c = yield* _([false])
const d = yield* _([a, b, c])
return d
})
expectType<Array<string | number | boolean>>(result)
expect(result).toEqual([1, 'a', false])
})
it('is equal to pipe notation', () => {
const result = Do(A.Monad)(function* (_) {
const a = yield* _([1, 2])
const b = yield* _(['a'])
const c = yield* _([false])
const d = yield* _([a, b, c])
return d
})
const result2 = pipe(
A.Do,
A.bind('a', () => [1, 2]),
A.bind('b', () => ['a']),
A.bind('c', () => [false]),
A.bind('d', ({ a, b, c }) => [a, b, c]),
A.map(({ d }) => {
return d
}),
)
expect(result).toEqual(result2)
})
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment