Repository: plaid/sanctuary (js)

Issue:
mapMaybe :: (a -> Maybe b) -> [a] -> [b]
<https://hackage.haskell.org/package/base-4.8.1.0/docs/Data-Maybe.html#v:mapMaybe>:

> The `mapMaybe` function is a version of `map` which can throw out elements. In particular, the functional argument returns something of type `Maybe b`. If this is `Nothing`, no element is added on to the result list. If it is `Just b`, then `b` is included in the result list.


Pull request:
maybe: add S.mapMaybe
Closes #73

`catMaybes :: [Maybe a] -> [a]` proved useful in defining this function. I'll rebase this patch once #78 has been merged.


Patch:
diff --git a/index.js b/index.js
index b197d4a..a3780c4 100644
--- a/index.js
+++ b/index.js
@@ -695,7 +695,25 @@
   //. > S.catMaybes([S.Just('foo'), S.Nothing(), S.Just('baz')])
   //. ["foo", "baz"]
   //. ```
-  S.catMaybes = def('catMaybes', [Accessible], R.chain(maybe([], R.of)));
+  var catMaybes = S.catMaybes =
+  def('catMaybes', [Accessible], R.chain(maybe([], R.of)));
+
+  //# mapMaybe :: (a -> Maybe b) -> [a] -> [b]
+  //.
+  //. Takes a function and a list, applies the function to each element of
+  //. the list, and returns a list of "successful" results. If the result of
+  //. applying the function to an element of the list is a Nothing, the result
+  //. is discarded; if the result is a Just, the Just's value is included in
+  //. the output list.
+  //.
+  //. In general terms, `mapMaybe` filters a list while mapping over it.
+  //.
+  //. ```javascript
+  //. > S.mapMaybe(S.head, [[], [1, 2, 3], [], [4, 5, 6], []])
+  //. [1, 4]
+  //. ```
+  S.mapMaybe =
+  def('mapMaybe', [Function, Accessible], R.compose(catMaybes, R.map));
 
   //# encase :: (* -> a) -> (* -> Maybe a)
   //.


Test patch:
diff --git a/test/index.js b/test/index.js
index 829bb67..9924409 100644
--- a/test/index.js
+++ b/test/index.js
@@ -710,6 +710,39 @@ describe('maybe', function() {
 
   });
 
+  describe('mapMaybe', function() {
+
+    it('is a binary function', function() {
+      eq(typeof S.mapMaybe, 'function');
+      eq(S.mapMaybe.length, 2);
+    });
+
+    it('type checks its arguments', function() {
+      assert.throws(function() { S.mapMaybe([1, 2, 3]); },
+                    errorEq(TypeError,
+                            '‘mapMaybe’ requires a value of type Function ' +
+                            'as its first argument; received [1, 2, 3]'));
+
+      assert.throws(function() { S.mapMaybe(S.head, null); },
+                    errorEq(TypeError,
+                            'The second argument to ‘mapMaybe’ ' +
+                            'cannot be null or undefined'));
+    });
+
+    it('maps over a list to produce a list of successful results', function() {
+      eq(S.mapMaybe(S.head, []), []);
+      eq(S.mapMaybe(S.head, [[], [], []]), []);
+      eq(S.mapMaybe(S.head, [[1, 2], [3, 4], [5, 6]]), [1, 3, 5]);
+      eq(S.mapMaybe(S.head, [[1], [], [3], [], [5], []]), [1, 3, 5]);
+    });
+
+    it('is curried', function() {
+      eq(S.mapMaybe(S.head).length, 1);
+      eq(S.mapMaybe(S.head)(['foo', '', 'bar', '', 'baz']), ['f', 'b', 'b']);
+    });
+
+  });
+
   describe('encase', function() {
 
     it('is a unary function', function() {