diff --git a/index.js b/index.js index ad7920c9..dc8e743c 100644 --- a/index.js +++ b/index.js @@ -973,7 +973,7 @@ [b, $Maybe(b)], Maybe.of); - //# Maybe#reduce :: Maybe a ~> (b -> a -> b) -> b -> b + //# Maybe#reduce :: Maybe a ~> ((b, a) -> b) -> b -> b //. //. Takes a function and an initial value of any type, and returns: //. @@ -1357,8 +1357,8 @@ //. `Either a b` is either a Left whose value is of type `a` or a Right whose //. value is of type `b`. //. - //. The Either type satisfies the [Semigroup][], [Monad][], and [Extend][] - //. specifications. + //. The Either type satisfies the [Semigroup][], [Monad][], [Foldable][], and + //. [Extend][] specifications. //# EitherType :: Type -> Type -> Type //. @@ -1591,6 +1591,30 @@ [c, $Either(a, c)], Either.of); + //# Either#reduce :: Either a b ~> ((c, b) -> c) -> c -> c + //. + //. Takes a function and an initial value of any type, and returns: + //. + //. - the initial value if `this` is a Left; otherwise + //. + //. - the result of applying the function to the initial value and this + //. Right's value. + //. + //. ```javascript + //. > S.Left('Cannot divide by zero').reduce((xs, x) => xs.concat([x]), [42]) + //. [42] + //. + //. > S.Right(5).reduce((xs, x) => xs.concat([x]), [42]) + //. [42, 5] + //. ``` + Either.prototype.reduce = + method('Either#reduce', + {}, + [$Either(a, b), $.Function, c, c], + function(either, f, x) { + return either.isRight ? f(x, either.value) : x; + }); + //# Either#toBoolean :: Either a b ~> Boolean //. //. Returns `false` if `this` is a Left; `true` if `this` is a Right. diff --git a/test/Either/Left.js b/test/Either/Left.js index 388dfe1b..7f0d92cf 100644 --- a/test/Either/Left.js +++ b/test/Either/Left.js @@ -167,6 +167,24 @@ describe('Left', function() { 'The value at position 1 is not a member of ‘Function’.\n')); }); + it('provides a "reduce" method', function() { + eq(S.Left().reduce.length, 2); + eq(S.Left().reduce(function(xs, x) { return xs.concat([x]); }, [42]), + [42]); + + throws(function() { S.Left().reduce(null, null); }, + errorEq(TypeError, + 'Invalid value\n' + + '\n' + + 'Either#reduce :: Either a b -> Function -> c -> c\n' + + ' ^^^^^^^^\n' + + ' 1\n' + + '\n' + + '1) null :: Null\n' + + '\n' + + 'The value at position 1 is not a member of ‘Function’.\n')); + }); + it('provides a "toBoolean" method', function() { eq(S.Left('abc').toBoolean.length, 0); eq(S.Left('abc').toBoolean(), false); diff --git a/test/Either/Right.js b/test/Either/Right.js index 405edc3f..df18aa5b 100644 --- a/test/Either/Right.js +++ b/test/Either/Right.js @@ -167,6 +167,24 @@ describe('Right', function() { 'The value at position 1 is not a member of ‘Function’.\n')); }); + it('provides a "reduce" method', function() { + eq(S.Right(5).reduce.length, 2); + eq(S.Right(5).reduce(function(xs, x) { return xs.concat([x]); }, [42]), + [42, 5]); + + throws(function() { S.Right().reduce(null, null); }, + errorEq(TypeError, + 'Invalid value\n' + + '\n' + + 'Either#reduce :: Either a b -> Function -> c -> c\n' + + ' ^^^^^^^^\n' + + ' 1\n' + + '\n' + + '1) null :: Null\n' + + '\n' + + 'The value at position 1 is not a member of ‘Function’.\n')); + }); + it('provides a "toBoolean" method', function() { eq(S.Right(42).toBoolean.length, 0); eq(S.Right(42).toBoolean(), true);