Repository: sanctuary-js/sanctuary
Pull request #490 (2018-02-20 14:42:25)

filterable: generalize S.justs, S.lefts, and S.rights
<https://github.com/sanctuary-js/sanctuary/issues/489#issuecomment-366982183>



Patch:
diff --git a/index.js b/index.js
index 68cf329..3466039 100644
--- a/index.js
+++ b/index.js
@@ -244,6 +244,11 @@
     };
   }
 
+  //  value :: { value :: a } -> a
+  function value(r) {
+    return r.value;
+  }
+
   //  readmeUrl :: String -> String
   function readmeUrl(id) {
     var version = '0.14.1';  // updated programmatically
@@ -2173,10 +2178,10 @@
   }
   S.maybe_ = def('maybe_', {}, [$.Thunk(b), Fn(a, b), $Maybe(a), b], maybe_);
 
-  //# justs :: Array (Maybe a) -> Array a
+  //# justs :: (Filterable f, Functor f) => f (Maybe a) -> f a
   //.
-  //. Takes an array of Maybes and returns an array containing each Just's
-  //. value. Equivalent to Haskell's `catMaybes` function.
+  //. Discards each element which is Nothing, and unwraps each element which is
+  //. a Just. Related to Haskell's `catMaybes` function.
   //.
   //. See also [`lefts`](#lefts) and [`rights`](#rights).
   //.
@@ -2185,12 +2190,10 @@
   //. ['foo', 'baz']
   //. ```
   function justs(maybes) {
-    return Z.reduce(function(xs, maybe) {
-      if (maybe.isJust) xs.push(maybe.value);
-      return xs;
-    }, [], maybes);
+    return Z.map(value, Z.filter(isJust, maybes));
   }
-  S.justs = def('justs', {}, [$.Array($Maybe(a)), $.Array(a)], justs);
+  S.justs =
+  def('justs', {f: [Z.Filterable, Z.Functor]}, [f($Maybe(a)), f(a)], justs);
 
   //# mapMaybe :: (a -> Maybe b) -> Array a -> Array b
   //.
@@ -2799,10 +2802,10 @@
   }
   S.either = def('either', {}, [Fn(a, c), Fn(b, c), $Either(a, b), c], either);
 
-  //# lefts :: Array (Either a b) -> Array a
+  //# lefts :: (Filterable f, Functor f) => f (Either a b) -> f a
   //.
-  //. Takes an array of Eithers and returns an array containing each Left's
-  //. value.
+  //. Discards each element which is a Right, and unwraps each element which is
+  //. a Left.
   //.
   //. See also [`rights`](#rights).
   //.
@@ -2811,17 +2814,18 @@
   //. ['foo', 'bar']
   //. ```
   function lefts(eithers) {
-    return Z.reduce(function(xs, either) {
-      if (either.isLeft) xs.push(either.value);
-      return xs;
-    }, [], eithers);
+    return Z.map(value, Z.filter(isLeft, eithers));
   }
-  S.lefts = def('lefts', {}, [$.Array($Either(a, b)), $.Array(a)], lefts);
+  S.lefts =
+  def('lefts',
+      {f: [Z.Filterable, Z.Functor]},
+      [f($Either(a, b)), f(a)],
+      lefts);
 
-  //# rights :: Array (Either a b) -> Array b
+  //# rights :: (Filterable f, Functor f) => f (Either a b) -> f b
   //.
-  //. Takes an array of Eithers and returns an array containing each Right's
-  //. value.
+  //. Discards each element which is a Left, and unwraps each element which is
+  //. a Right.
   //.
   //. See also [`lefts`](#lefts).
   //.
@@ -2830,12 +2834,13 @@
   //. [20, 10]
   //. ```
   function rights(eithers) {
-    return Z.reduce(function(xs, either) {
-      if (either.isRight) xs.push(either.value);
-      return xs;
-    }, [], eithers);
+    return Z.map(value, Z.filter(isRight, eithers));
   }
-  S.rights = def('rights', {}, [$.Array($Either(a, b)), $.Array(b)], rights);
+  S.rights =
+  def('rights',
+      {f: [Z.Filterable, Z.Functor]},
+      [f($Either(a, b)), f(b)],
+      rights);
 
   //# tagBy :: (a -> Boolean) -> a -> Either a a
   //.


Test patch:
diff --git a/test/justs.js b/test/justs.js
index 152afe0..b2280f5 100644
--- a/test/justs.js
+++ b/test/justs.js
@@ -1,15 +1,20 @@
 'use strict';
 
-var S = require('..');
+var S = require('./internal/sanctuary');
 
+var List = require('./internal/List');
 var eq = require('./internal/eq');
 
 
+var Cons = List.Cons;
+var Nil = List.Nil;
+
+
 test('justs', function() {
 
   eq(typeof S.justs, 'function');
   eq(S.justs.length, 1);
-  eq(S.justs.toString(), 'justs :: Array (Maybe a) -> Array a');
+  eq(S.justs.toString(), 'justs :: (Filterable f, Functor f) => f (Maybe a) -> f a');
 
   eq(S.justs([]), []);
   eq(S.justs([S.Nothing, S.Nothing]), []);
@@ -17,4 +22,20 @@ test('justs', function() {
   eq(S.justs([S.Just('a'), S.Nothing]), ['a']);
   eq(S.justs([S.Just('a'), S.Just('b')]), ['a', 'b']);
 
+  eq(S.justs({}), {});
+  eq(S.justs({x: S.Nothing, y: S.Nothing}), {});
+  eq(S.justs({x: S.Nothing, y: S.Just(2)}), {y: 2});
+  eq(S.justs({x: S.Just(1), y: S.Nothing}), {x: 1});
+  eq(S.justs({x: S.Just(1), y: S.Just(2)}), {x: 1, y: 2});
+
+  eq(S.justs(S.Nothing), S.Nothing);
+  eq(S.justs(S.Just(S.Nothing)), S.Nothing);
+  eq(S.justs(S.Just(S.Just(1))), S.Just(1));
+
+  eq(S.justs(Nil), Nil);
+  eq(S.justs(Cons(S.Nothing, Cons(S.Nothing, Nil))), Nil);
+  eq(S.justs(Cons(S.Nothing, Cons(S.Just(2), Nil))), Cons(2, Nil));
+  eq(S.justs(Cons(S.Just(1), Cons(S.Nothing, Nil))), Cons(1, Nil));
+  eq(S.justs(Cons(S.Just(1), Cons(S.Just(2), Nil))), Cons(1, Cons(2, Nil)));
+
 });
diff --git a/test/lefts.js b/test/lefts.js
index 191d761..2736aeb 100644
--- a/test/lefts.js
+++ b/test/lefts.js
@@ -1,15 +1,20 @@
 'use strict';
 
-var S = require('..');
+var S = require('./internal/sanctuary');
 
+var List = require('./internal/List');
 var eq = require('./internal/eq');
 
 
+var Cons = List.Cons;
+var Nil = List.Nil;
+
+
 test('lefts', function() {
 
   eq(typeof S.lefts, 'function');
   eq(S.lefts.length, 1);
-  eq(S.lefts.toString(), 'lefts :: Array (Either a b) -> Array a');
+  eq(S.lefts.toString(), 'lefts :: (Filterable f, Functor f) => f (Either a b) -> f a');
 
   eq(S.lefts([]), []);
   eq(S.lefts([S.Right(2), S.Right(1)]), []);
@@ -17,4 +22,20 @@ test('lefts', function() {
   eq(S.lefts([S.Left('a'), S.Right(1)]), ['a']);
   eq(S.lefts([S.Left('a'), S.Left('b')]), ['a', 'b']);
 
+  eq(S.lefts({}), {});
+  eq(S.lefts({x: S.Right(2), y: S.Right(1)}), {});
+  eq(S.lefts({x: S.Right(2), y: S.Left('b')}), {y: 'b'});
+  eq(S.lefts({x: S.Left('a'), y: S.Right(1)}), {x: 'a'});
+  eq(S.lefts({x: S.Left('a'), y: S.Left('b')}), {x: 'a', y: 'b'});
+
+  eq(S.lefts(S.Nothing), S.Nothing);
+  eq(S.lefts(S.Just(S.Right(1))), S.Nothing);
+  eq(S.lefts(S.Just(S.Left('a'))), S.Just('a'));
+
+  eq(S.lefts(Nil), Nil);
+  eq(S.lefts(Cons(S.Right(2), Cons(S.Right(1), Nil))), Nil);
+  eq(S.lefts(Cons(S.Right(2), Cons(S.Left('b'), Nil))), Cons('b', Nil));
+  eq(S.lefts(Cons(S.Left('a'), Cons(S.Right(1), Nil))), Cons('a', Nil));
+  eq(S.lefts(Cons(S.Left('a'), Cons(S.Left('b'), Nil))), Cons('a', Cons('b', Nil)));
+
 });
diff --git a/test/rights.js b/test/rights.js
index 3785d44..4a4695c 100644
--- a/test/rights.js
+++ b/test/rights.js
@@ -1,15 +1,20 @@
 'use strict';
 
-var S = require('..');
+var S = require('./internal/sanctuary');
 
+var List = require('./internal/List');
 var eq = require('./internal/eq');
 
 
+var Cons = List.Cons;
+var Nil = List.Nil;
+
+
 test('rights', function() {
 
   eq(typeof S.rights, 'function');
   eq(S.rights.length, 1);
-  eq(S.rights.toString(), 'rights :: Array (Either a b) -> Array b');
+  eq(S.rights.toString(), 'rights :: (Filterable f, Functor f) => f (Either a b) -> f b');
 
   eq(S.rights([]), []);
   eq(S.rights([S.Left('a'), S.Left('b')]), []);
@@ -17,4 +22,20 @@ test('rights', function() {
   eq(S.rights([S.Right(2), S.Left('b')]), [2]);
   eq(S.rights([S.Right(2), S.Right(1)]), [2, 1]);
 
+  eq(S.rights({}), {});
+  eq(S.rights({x: S.Left('a'), y: S.Left('b')}), {});
+  eq(S.rights({x: S.Left('a'), y: S.Right(1)}), {y: 1});
+  eq(S.rights({x: S.Right(2), y: S.Left('b')}), {x: 2});
+  eq(S.rights({x: S.Right(2), y: S.Right(1)}), {x: 2, y: 1});
+
+  eq(S.rights(S.Nothing), S.Nothing);
+  eq(S.rights(S.Just(S.Left('a'))), S.Nothing);
+  eq(S.rights(S.Just(S.Right(1))), S.Just(1));
+
+  eq(S.rights(Nil), Nil);
+  eq(S.rights(Cons(S.Left('a'), Cons(S.Left('b'), Nil))), Nil);
+  eq(S.rights(Cons(S.Left('a'), Cons(S.Right(1), Nil))), Cons(1, Nil));
+  eq(S.rights(Cons(S.Right(2), Cons(S.Left('b'), Nil))), Cons(2, Nil));
+  eq(S.rights(Cons(S.Right(2), Cons(S.Right(1), Nil))), Cons(2, Cons(1, Nil)));
+
 });