Repository: sanctuary-js/sanctuary (js)

Issue:
mapMaybe on something else than an array ?
Hello,

Currently this is available:
```js
S.map(S.get(S.is(Number), 'c'))({a: {c: 1}, b: {}}); // {a: Just(1), b: Nothing}
```

But this is not:
```js
S.mapMaybe(S.get(S.is(Number), 'c'))({a: {c: 1}, b: {}}); // would return {a: 1}
```

I was wondering if `mapMaybe` could be extended to operate the same way as `map`?

Pull request description:
maybe: generalize S.mapMaybe
Closes #489

/cc @gautaz


Patch:
diff --git a/index.js b/index.js
index 3466039..70008cd 100644
--- a/index.js
+++ b/index.js
@@ -2195,25 +2195,28 @@
   S.justs =
   def('justs', {f: [Z.Filterable, Z.Functor]}, [f($Maybe(a)), f(a)], justs);
 
-  //# mapMaybe :: (a -> Maybe b) -> Array a -> Array b
+  //# mapMaybe :: (Filterable f, Functor f) => (a -> Maybe b) -> f a -> f b
   //.
-  //. Takes a function and an array, applies the function to each element of
-  //. the array, and returns an array of "successful" results. If the result of
-  //. applying the function to an element of the array is Nothing, the result
-  //. is discarded; if the result is a Just, the Just's value is included in
-  //. the output array.
-  //.
-  //. In general terms, `mapMaybe` filters an array while mapping over it.
+  //. Takes a function and a structure, applies the function to each element
+  //. of the structure, and returns the "successful" results. If the result of
+  //. applying the function to an element is Nothing, the result is discarded;
+  //. if the result is a Just, the Just's value is included.
   //.
   //. ```javascript
   //. > S.mapMaybe(S.head, [[], [1, 2, 3], [], [4, 5, 6], []])
   //. [1, 4]
+  //.
+  //. > S.mapMaybe(S.head, {x: [1, 2, 3], y: [], z: [4, 5, 6]})
+  //. {x: 1, z: 4}
   //. ```
-  function mapMaybe(f, xs) {
-    return justs(Z.map(f, xs));
+  function mapMaybe(f, filterable) {
+    return justs(Z.map(f, filterable));
   }
   S.mapMaybe =
-  def('mapMaybe', {}, [Fn(a, $Maybe(b)), $.Array(a), $.Array(b)], mapMaybe);
+  def('mapMaybe',
+      {f: [Z.Filterable, Z.Functor]},
+      [Fn(a, $Maybe(b)), f(a), f(b)],
+      mapMaybe);
 
   //# encase :: (a -> b) -> a -> Maybe b
   //.


Test patch:
diff --git a/test/mapMaybe.js b/test/mapMaybe.js
index 5baf7ad..8c95808 100644
--- a/test/mapMaybe.js
+++ b/test/mapMaybe.js
@@ -1,19 +1,38 @@
 '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('mapMaybe', function() {
 
   eq(typeof S.mapMaybe, 'function');
   eq(S.mapMaybe.length, 2);
-  eq(S.mapMaybe.toString(), 'mapMaybe :: (a -> Maybe b) -> Array a -> Array b');
+  eq(S.mapMaybe.toString(), 'mapMaybe :: (Filterable f, Functor f) => (a -> Maybe b) -> f a -> f b');
 
   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]);
 
+  eq(S.mapMaybe(S.head, {}), {});
+  eq(S.mapMaybe(S.head, {a: [], b: [], c: []}), {});
+  eq(S.mapMaybe(S.head, {a: [1, 2], b: [3, 4], c: [5, 6]}), {a: 1, b: 3, c: 5});
+  eq(S.mapMaybe(S.head, {a: [1], b: [], c: [3], d: [], e: [5], f: []}), {a: 1, c: 3, e: 5});
+
+  eq(S.mapMaybe(S.head, S.Nothing), S.Nothing);
+  eq(S.mapMaybe(S.head, S.Just([])), S.Nothing);
+  eq(S.mapMaybe(S.head, S.Just([1, 2])), S.Just(1));
+
+  eq(S.mapMaybe(S.head, Nil), Nil);
+  eq(S.mapMaybe(S.head, Cons([], Cons([], Cons([], Nil)))), Nil);
+  eq(S.mapMaybe(S.head, Cons([1, 2], Cons([3, 4], Cons([5, 6], Nil)))), Cons(1, Cons(3, Cons(5, Nil))));
+  eq(S.mapMaybe(S.head, Cons([1], Cons([], Cons([3], Cons([], Cons([5], Cons([], Nil))))))), Cons(1, Cons(3, Cons(5, Nil))));
+
 });