Skip to content

Commit

Permalink
feat: chain rec
Browse files Browse the repository at this point in the history
  • Loading branch information
Hfutsora committed Jul 19, 2022
1 parent 56bffef commit c954bf8
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 4 deletions.
30 changes: 30 additions & 0 deletions src/Either.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,36 @@ export const getRight = <E, A>(ma: Either<E, A>): Maybe<A> =>
export const chain = <E2, A, B>(f: (a: A) => Either<E2, B>) => <E1>(ma: Either<E1, A>): Either<E1 | E2, B> =>
isLeft(ma) ? ma : f(ma.right)

/**
* Chains recursively until the loop is done.
*
* @example
*
* ```ts
* assert.deepStrictEqual(
* pipe(
* right(1),
* chainRec(
* a => a + 1,
* b => b > 4,
* )
* )
* , right(5))
* ```
*/
export const chainRec: <E, A, B>(loop: (a: A) => B, done: Predicate<B>) => (init: Either<E, A>) => Either<E, B> = (loop, done) => // FIXME: loop generics
init => {
if(isLeft(init)) return init

let next = loop(init.right)

while(!done(next)) {
next = loop(next as any)
}

return right(next)
}

/**
* Returns `Either` if it's a `Right`, otherwise returns onLeft result.
*
Expand Down
9 changes: 6 additions & 3 deletions src/Functors/ChainRec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@

import { HKT, URIS, KindOf } from './HKT'
import { Chain, Chain1, Chain2 } from './Chain'
import { Predicate } from '../Predicate'

// TODO:

export interface ChainRec<F> extends Chain<F> {
readonly chainRec: <A, B, C>(f: (loop: (a: A) => C, done: (b: B) => C, init: A) => HKT<F, C>, fi: A) => HKT<F, B>
readonly chainRec: <A, B>(init: A, f: (a: A) => HKT<F, A>) => HKT<F, B>
}

export interface ChainRec1<F extends URIS> extends Chain1<F> {
readonly chainRec: <A, B, C>(f: (loop: (a: A) => C, done: (b: B) => C, init: A) => KindOf<F, [C]>, fi: A) => KindOf<F, [B]>
readonly chainRec: <A, B>(init: A, f: (a: A) => KindOf<F, [A, B]>) => KindOf<F, [B]>
}

export interface ChainRec2<F extends URIS> extends Chain2<F> {
readonly chainRec: <E, A, B, C>(f: (loop: (a: A) => C, done: (b: B) => C, init: A) => KindOf<F, [E, C]>, fi: A) => KindOf<F, [E, B]>
readonly chainRec: <E, A, B, C>(loop: (a: A) => C, done: Predicate<B>, init: KindOf<F, [E, A]>) => KindOf<F, [E, B]>
}
37 changes: 36 additions & 1 deletion test/Either.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isLeft, left, right, isRight, map, of, fromPredicate, match, getOrElse, chain, orElse, exists, alt, getLeft, getRight, fromMaybe, tryCatch, swap, equals, ap } from '../src/Either'
import { isLeft, left, right, isRight, map, of, fromPredicate, match, getOrElse, chain, orElse, exists, alt, getLeft, getRight, fromMaybe, tryCatch, swap, equals, ap, chainRec } from '../src/Either'
import { flow, pipe } from '../src/Pipe'
import { none, some } from '../src/Maybe'

Expand Down Expand Up @@ -91,6 +91,41 @@ test('chain', () => {
expect(chain((n: number) => right(n + 1))(right(1))).toEqual(right(2))
})

test('chainRec', () => {
expect(
pipe(
right(1),
chainRec(
a => a + 1,
b => b > 4,
),

)
).toEqual(right(5))

expect(
pipe(
right(1),
chainRec(
a => a < 5 ? a + 1 : `${5}`,
b => typeof b === 'string',
),

)
).toEqual(right('5'))

expect(
pipe(
left(1),
chainRec(
a => a + 1,
b => b > 4,
),

)
).toEqual(left(1))
})

test('orElse', () => {
expect(orElse((n: number) => right(n + 1))(left(1))).toEqual(right(2))
expect(orElse((n: number) => right(n + 1))(right(1))).toEqual(right(1))
Expand Down

0 comments on commit c954bf8

Please sign in to comment.