Repository: plaid/sanctuary
Pull request #114 (2015-11-07 11:08:05)

Add allPass and anyPass
The most elegant implementation of these would be something like:

```js
S.allPass = (preds, a) => R.reduce((acc, f) => f(a) && acc, true, preds);
```
But then we'd lose short-circuiting.

kedashoe: > But then we'd lose short-circuiting.

You could use `R.reduced`

```js
let allPass = (preds, a) => R.reduce((acc, f) => f(a) || R.reduced(false), true, preds);
let count = 0;
[
  allPass([
    () => ++count && true,
    () => ++count && false,
    () => ++count && true
  ]),
  allPass([
    () => ++count && true,
    () => ++count && true,
    () => ++count && true
  ]),
  count
]
```
http://goo.gl/gl1pio
svozza: Nice!
svozza: Ah, that desn't seem to play well with Sanctuary's type checking. The original works fine but when wrapped in the `def` function:
```
TypeError: ‘test’ requires a value of type String as its second argument; received {"name": "a"}
```
davidchambers: We should test the handling of an empty predicates list.

svozza: What should the expected output be? An error? Or given that no predicates were passed in then none passed so it should always be false.
davidchambers: `S.allPass([], x)` should evaluate `true` for every `x`: all (zero) of the predicates are guaranteed to be satisfied.

`S.anyPass([], x)` should evaluate `false` for every `x`: it's impossible for at least one predicate to be satisfied.

svozza: Cool. Makes sense.
davidchambers: I'm sorry for getting behind on reviewing your pull requests, Stefano. Could you rebase and resolve the merge conflicts?

svozza: I missed this comment altogether, just rebased now.

Patch:
diff --git a/index.js b/index.js
index a26fb33..835b930 100644
--- a/index.js
+++ b/index.js
@@ -1624,6 +1624,48 @@
     return pred(x) ? f(x) : g(x);
   });
 
+  //# allPass :: [a -> Boolean] -> a -> Boolean
+  //.
+  //. Takes an array of unary predicates and a value of any type
+  //. and returns `true` if all the predicates pass; `false` otherwise.
+  //. None of the subsequent predicates will be evaluated after the
+  //. first failed predicate.
+  //.
+  //. ```javascript
+  //. > S.allPass([S.test(/q/), S.test(/u/), S.test(/i/)], 'quiessence')
+  //. true
+  //.
+  //. > S.allPass([S.test(/q/), S.test(/u/), S.test(/i/)], 'fissiparous')
+  //. false
+  //. ```
+  S.allPass = def('allPass', [Array, a], function(preds, x) {
+    for (var idx = 0; idx < preds.length; idx += 1) {
+      if (!preds[idx](x)) return false;
+    }
+    return true;
+  });
+
+  //# anyPass :: [a -> Boolean] -> a -> Boolean
+  //.
+  //. Takes an array of unary predicates and a value of any type
+  //. and returns `true` if any of the predicates pass; `false` otherwise.
+  //. None of the subsequent predicates will be evaluated after the
+  //. first passed predicate.
+  //.
+  //. ```javascript
+  //. > S.anyPass([S.test(/q/), S.test(/u/), S.test(/i/)], 'incandescent')
+  //. true
+  //.
+  //. > S.anyPass([S.test(/q/), S.test(/u/), S.test(/i/)], 'empathy')
+  //. false
+  //. ```
+  S.anyPass = def('anyPass', [Array, a], function(preds, x) {
+    for (var idx = 0; idx < preds.length; idx += 1) {
+      if (preds[idx](x)) return true;
+    }
+    return false;
+  });
+
   //. ### List
 
   //# slice :: Integer -> Integer -> [a] -> Maybe [a]


Test patch:
diff --git a/test/index.js b/test/index.js
index 85b9c5d..3cbd6f2 100644
--- a/test/index.js
+++ b/test/index.js
@@ -2175,6 +2175,164 @@ describe('control', function() {
 
   });
 
+  describe('allPass', function() {
+
+    it('is a binary function', function() {
+      eq(typeof S.allPass, 'function');
+      eq(S.allPass.length, 2);
+    });
+
+    it('type checks its arguments', function() {
+      assert.throws(function() { S.allPass('wrong'); },
+                    errorEq(TypeError,
+                            '‘allPass’ requires a value of type Array ' +
+                            'as its first argument; received "wrong"'));
+    });
+
+    it('returns true when given an empty array of predicates', function() {
+      eq(S.allPass([], 'quiessence'), true);
+    });
+
+    it('returns true when all predicates pass', function() {
+      eq(S.allPass([S.test(/q/),
+                    S.test(/u/),
+                    S.test(/i/),
+                    S.test(/c/)],
+                   'quiessence'),
+         true);
+    });
+
+    it('returns false when some predicates fail', function() {
+      eq(S.allPass([S.test(/q/),
+                    S.test(/u/),
+                    S.test(/i/),
+                    S.test(/c/)],
+                   'quintessential'),
+         false);
+
+      eq(S.allPass([S.test(/q/),
+                    S.test(/u/),
+                    S.test(/i/),
+                    S.test(/c/)],
+                   'incandescent'),
+         false);
+
+      eq(S.allPass([S.test(/q/),
+                    S.test(/u/),
+                    S.test(/i/),
+                    S.test(/c/)],
+                   'fissiparous'),
+         false);
+    });
+
+    it('returns false when all predicates fail', function() {
+      eq(S.allPass([S.test(/q/),
+                    S.test(/u/),
+                    S.test(/i/),
+                    S.test(/c/)],
+                   'empathy'),
+         false);
+    });
+
+    it('short-circuits when one predicate fails', function() {
+      var evaluated = false;
+      var evaluate = function() { evaluated = true; };
+
+      eq(S.allPass([S.test(/q/),
+                    S.test(/u/),
+                    S.test(/i/),
+                    evaluate],
+                   'incandescent'),
+         false);
+      eq(evaluated, false);
+    });
+
+    it('is curried', function() {
+      eq(S.allPass([S.test(/q/)]).length, 1);
+      eq(S.allPass([S.test(/q/)])('quiessence'), true);
+    });
+
+  });
+
+  describe('anyPass', function() {
+
+    it('is a binary function', function() {
+      eq(typeof S.anyPass, 'function');
+      eq(S.anyPass.length, 2);
+    });
+
+    it('type checks its arguments', function() {
+      assert.throws(function() { S.anyPass('wrong'); },
+                    errorEq(TypeError,
+                            '‘anyPass’ requires a value of type Array ' +
+                            'as its first argument; received "wrong"'));
+    });
+
+    it('returns false when given an empty array of predicates', function() {
+      eq(S.anyPass([], 'quiessence'), false);
+    });
+
+    it('returns true when all predicates pass', function() {
+      eq(S.anyPass([S.test(/q/),
+                    S.test(/u/),
+                    S.test(/i/),
+                    S.test(/c/)],
+                   'quiessence'),
+         true);
+    });
+
+    it('returns true when some predicates pass', function() {
+      eq(S.anyPass([S.test(/q/),
+                    S.test(/u/),
+                    S.test(/i/),
+                    S.test(/c/)],
+                   'quintessential'),
+         true);
+
+      eq(S.anyPass([S.test(/q/),
+                    S.test(/u/),
+                    S.test(/i/),
+                    S.test(/c/)],
+                   'incandescent'),
+         true);
+
+      eq(S.anyPass([S.test(/q/),
+                    S.test(/u/),
+                    S.test(/i/),
+                    S.test(/c/)],
+                   'fissiparous'),
+         true);
+    });
+
+    it('returns false when all predicates fail', function() {
+      eq(S.anyPass([S.test(/q/),
+                    S.test(/u/),
+                    S.test(/i/),
+                    S.test(/c/)],
+                   'empathy'),
+         false);
+    });
+
+    it('it short-circuits when one predicate passes', function() {
+      var evaluated = false;
+      var evaluate = function() { evaluated = true; };
+
+      eq(S.anyPass([S.test(/q/),
+                    evaluate,
+                    S.test(/i/),
+                    S.test(/c/)],
+                   'quiessence'),
+         true);
+      eq(evaluated, false);
+    });
+
+    it('is curried', function() {
+      eq(S.anyPass([S.test(/q/)]).length, 1);
+      eq(S.anyPass([S.test(/q/)])('quiessence'), true);
+    });
+
+  });
+
 });
 
 describe('list', function() {