Repository: plaid/sanctuary
Pull request #144 (2016-02-03 01:53:57)

either: add S.lefts and S.rights
This pull request adds Haskell's [`lefts`][1] and [`rights`][2]:

```haskell
lefts  :: [Either a b] -> [a]
rights :: [Either a b] -> [b]
```

I recently reviewed some code @Bpless wrote which could be nicely expressed in terms of `rights`.

These functions are similar to [`S.catMaybes`][3], which should arguably be renamed `S.justs` for consistency.


[1]: http://hackage.haskell.org/package/base-4.8.2.0/docs/Data-Either.html#v:lefts
[2]: http://hackage.haskell.org/package/base-4.8.2.0/docs/Data-Either.html#v:rights
[3]: https://github.com/plaid/sanctuary#catMaybes



Patch:
diff --git a/index.js b/index.js
index 7d62148..0d648c7 100644
--- a/index.js
+++ b/index.js
@@ -1490,6 +1490,42 @@
         return either.isLeft ? l(either.value) : r(either.value);
       });
 
+  //# lefts :: [Either a b] -> [a]
+  //.
+  //. Takes a list of Eithers and returns a list containing each Left's value.
+  //.
+  //. See also [`rights`](#rights).
+  //.
+  //. ```javascript
+  //. > S.lefts([S.Right(20), S.Left('foo'), S.Right(10), S.Left('bar')])
+  //. ['foo', 'bar']
+  //. ```
+  S.lefts =
+  def('lefts',
+      {},
+      [$.Array($Either(a, b)), $.Array(a)],
+      R.chain(function(either) {
+        return either.isLeft ? [either.value] : [];
+      }));
+
+  //# rights :: [Either a b] -> [b]
+  //.
+  //. Takes a list of Eithers and returns a list containing each Right's value.
+  //.
+  //. See also [`lefts`](#lefts).
+  //.
+  //. ```javascript
+  //. > S.rights([S.Right(20), S.Left('foo'), S.Right(10), S.Left('bar')])
+  //. [20, 10]
+  //. ```
+  S.rights =
+  def('rights',
+      {},
+      [$.Array($Either(a, b)), $.Array(b)],
+      R.chain(function(either) {
+        return either.isRight ? [either.value] : [];
+      }));
+
   //# encaseEither :: (Error -> l) -> (a -> r) -> a -> Either l r
   //.
   //. Takes two unary functions, `f` and `g`, the second of which may throw,


Test patch:
diff --git a/test/index.js b/test/index.js
index c0bb809..53c510a 100644
--- a/test/index.js
+++ b/test/index.js
@@ -1919,6 +1919,56 @@ describe('either', function() {
 
   });
 
+  describe('lefts', function() {
+
+    it('is a unary function', function() {
+      eq(typeof S.lefts, 'function');
+      eq(S.lefts.length, 1);
+    });
+
+    it('type checks its arguments', function() {
+      assert.throws(function() { S.lefts([1, 2, 3]); },
+                    errorEq(TypeError,
+                            '‘lefts’ expected a value of type ' +
+                            '(Array (Either a b)) as its first argument; ' +
+                            'received [1, 2, 3]'));
+    });
+
+    it('returns a list containing the value of each Left', function() {
+      eq(S.lefts([]), []);
+      eq(S.lefts([S.Right(2), S.Right(1)]), []);
+      eq(S.lefts([S.Right(2), S.Left('b')]), ['b']);
+      eq(S.lefts([S.Left('a'), S.Right(1)]), ['a']);
+      eq(S.lefts([S.Left('a'), S.Left('b')]), ['a', 'b']);
+    });
+
+  });
+
+  describe('rights', function() {
+
+    it('is a unary function', function() {
+      eq(typeof S.rights, 'function');
+      eq(S.rights.length, 1);
+    });
+
+    it('type checks its arguments', function() {
+      assert.throws(function() { S.rights([1, 2, 3]); },
+                    errorEq(TypeError,
+                            '‘rights’ expected a value of type ' +
+                            '(Array (Either a b)) as its first argument; ' +
+                            'received [1, 2, 3]'));
+    });
+
+    it('returns a list containing the value of each Right', function() {
+      eq(S.rights([]), []);
+      eq(S.rights([S.Left('a'), S.Left('b')]), []);
+      eq(S.rights([S.Left('a'), S.Right(1)]), [1]);
+      eq(S.rights([S.Right(2), S.Left('b')]), [2]);
+      eq(S.rights([S.Right(2), S.Right(1)]), [2, 1]);
+    });
+
+  });
+
   describe('encaseEither', function() {
 
     it('is a ternary function', function() {