Repository: plaid/sanctuary
Pull request #10 (2015-02-13 07:37:38)

maybe: define "filter"
This makes Maybe compatible with `R.filter` and, if ramda/ramda#799 is merged, with `R.reject`.

@joneshf, would you mind reviewing this to make sure I faithfully translated the first of the laws you gave me?


joneshf: Looks like a good implementation. 

I'm not sure what your overall idea is here, but I'd like to think that you could implement `filter` once and for all if you know something is both a `Monad` and a `Monoid`:

```javascript
var filter = function(p, m) {
  return m.chain(function(x) {
    return p(x) ? m.of(x) : m.empty();
  });
}
```

Then you could put this on the prototype of whatever.

```javascript
...
Maybe.prototype.filter = function(p) {
  return filter(p, this);
}
...
```

So you don't end up having to write something similar for other types that are `Monad` and `Monoid`. If you're not planning on using the fantasyland stuff then forget about it.
davidchambers: > I'd like to think that you could implement `filter` once and for all if you know something is both a `Monad` and a `Monoid`

Great idea! Once I merge #8 Maybe will implement the Monad specification. I'll then open a pull request to have it implement the Monoid specification. Once I merge *that* pull request I'll update this one to use your `filter` function. :)


Patch:
diff --git a/index.js b/index.js
index 92dcc92..5c7cb60 100644
--- a/index.js
+++ b/index.js
@@ -40,6 +40,12 @@
     Child.super_ = Parent.prototype;
   };
 
+  var filter = function(pred, m) {
+    return m.chain(function(x) {
+      return pred(x) ? m.of(x) : m.empty();
+    });
+  };
+
   //  maybe  /////////////////////////////////////////////////////////////////
 
   function Maybe() {
@@ -58,6 +64,11 @@
 
   Maybe.prototype.empty = Maybe.empty;
 
+  //  Maybe#filter :: (a -> Boolean) -> m a
+  Maybe.prototype.filter = function(pred) {
+    return filter(pred, this);
+  };
+
   Maybe.prototype.of = Maybe.of;
 
   Maybe.prototype.type = Maybe;


Test patch:
diff --git a/test/index.js b/test/index.js
index a8760ff..5c77b70 100644
--- a/test/index.js
+++ b/test/index.js
@@ -9,6 +9,12 @@ var S = require('..');
 
 var eq = assert.strictEqual;
 
+//  T :: a -> Boolean
+var T = function(x) { return true; };  // jshint ignore:line
+
+//  F :: a -> Boolean
+var F = function(x) { return false; };  // jshint ignore:line
+
 //  identity :: a -> a
 var identity = function(x) { return x; };
 
@@ -87,6 +93,23 @@ describe('maybe', function() {
       eq(nothing.equals(S.Just(42)), false);
     });
 
+    it('provides a "filter" method', function() {
+      var nothing = S.Nothing();
+      eq(nothing.filter.length, 1);
+      assert(nothing.filter(T).equals(S.Nothing()));
+      assert(nothing.filter(F).equals(S.Nothing()));
+
+      var m = S.Nothing();
+      var f = function(n) { return n * n; };
+      var p = function(n) { return n < 0; };
+      var q = function(n) { return n > 0; };
+
+      assert(m.map(f).filter(p)
+             .equals(m.filter(function(x) { return p(f(x)); }).map(f)));
+      assert(m.map(f).filter(q)
+             .equals(m.filter(function(x) { return q(f(x)); }).map(f)));
+    });
+
     it('provides a "map" method', function() {
       var nothing = S.Nothing();
       eq(nothing.map.length, 1);
@@ -238,6 +261,25 @@ describe('maybe', function() {
       eq(just.equals(S.Nothing()), false);
     });
 
+    it('provides a "filter" method', function() {
+      var just = S.Just(42);
+      eq(just.filter.length, 1);
+      assert(just.filter(T).equals(S.Just(42)));
+      assert(just.filter(F).equals(S.Nothing()));
+      assert(just.filter(function(n) { return n > 0; }).equals(S.Just(42)));
+      assert(just.filter(function(n) { return n < 0; }).equals(S.Nothing()));
+
+      var m = S.Just(-5);
+      var f = function(n) { return n * n; };
+      var p = function(n) { return n < 0; };
+      var q = function(n) { return n > 0; };
+
+      assert(m.map(f).filter(p)
+             .equals(m.filter(function(x) { return p(f(x)); }).map(f)));
+      assert(m.map(f).filter(q)
+             .equals(m.filter(function(x) { return q(f(x)); }).map(f)));
+    });
+
     it('provides a "map" method', function() {
       var just = S.Just(42);
       eq(just.map.length, 1);
@@ -682,9 +724,6 @@ describe('list', function() {
 
   describe('find', function() {
 
-    var T = function() { return true; };
-    var F = function() { return false; };
-
     it('returns Just the first element satisfying the predicate', function() {
       assert(S.find(T, [null]).equals(S.Just(null)));
       assert(S.find(function(n) { return n >= 0; }, [-1, 0, 1])