Repository: plaid/sanctuary
Pull request #136 (2016-01-29 00:32:24)

function: assume that Function satisfies the requirements of Apply
[`R.map`][1] and [`R.ap`][2] support Function, so we can assume that Function satisfies the requirements of [Apply][3].

This allows us to define `complement`, `both`, and `either` via lifting, as discussed in #102.

```javascript
//    complement :: Functor f => f Boolean -> f Boolean
const complement = S.lift(S.not);

//    both :: Apply f => f Boolean -> f Boolean -> f Boolean
const both = S.lift2(S.and);

//    either :: Apply f => f Boolean -> f Boolean -> f Boolean
const either = S.lift2(S.or);

//    isEven :: Integer -> Boolean
const isEven = x => x % 2 === 0;

//    isOdd :: Integer -> Boolean
const isOdd = complement(isEven);

//    isPositive :: Number -> Boolean
const isPositive = x => x > 0;

both(isEven, isPositive)(42);   // => true
both(isEven, isPositive)(43);   // => false
both(isEven, isPositive)(-42);  // => false
both(isEven, isPositive)(-43);  // => false
```

/cc @benperez


[1]: https://github.com/ramda/ramda/blob/v0.19.1/src/map.js#L44-L47
[2]: https://github.com/ramda/ramda/blob/v0.19.1/src/ap.js#L30-L33
[3]: https://github.com/fantasyland/fantasy-land#apply


benperez: :dromedary_camel: Nice fix!

Patch:
diff --git a/index.js b/index.js
index 780e8a3..7d62148 100644
--- a/index.js
+++ b/index.js
@@ -146,7 +146,7 @@
   var Apply = $.TypeClass(
     'sanctuary/Apply',
     function(x) {
-      return _type(x) === 'Array' ||
+      return R.contains(_type(x), ['Array', 'Function']) ||
              Functor.test(x) && hasMethod('ap')(x);
     }
   );
@@ -155,7 +155,7 @@
   var Functor = $.TypeClass(
     'sanctuary/Functor',
     function(x) {
-      return _type(x) === 'Array' ||
+      return R.contains(_type(x), ['Array', 'Function']) ||
              hasMethod('map')(x);
     }
   );


Test patch:
diff --git a/test/index.js b/test/index.js
index 0eb7b6a..c0bb809 100644
--- a/test/index.js
+++ b/test/index.js
@@ -49,6 +49,9 @@ var parseHex = function(s) {
   return n !== n ? S.Left('Invalid hexadecimal string') : S.Right(n);
 };
 
+//  positive :: Number -> Boolean
+var positive = function(n) { return n > 0; };
+
 //  rem :: (Number, Number) -> Number !
 var rem = function(x, y) {
   if (y === 0) {
@@ -381,6 +384,9 @@ describe('function', function() {
 
       eq(S.lift(R.multiply(2), [1, 2, 3]), [2, 4, 6]);
       eq(S.lift(R.multiply(2), []), []);
+
+      eq(S.lift(S.not, S.even)(42), false);
+      eq(S.lift(S.not, S.even)(43), true);
     });
 
   });
@@ -408,6 +414,11 @@ describe('function', function() {
 
       eq(S.lift2(R.add, [1, 2], [10, 20]), [11, 21, 12, 22]);
       eq(S.lift2(R.add, [], [1, 2]), []);
+
+      eq(S.lift2(S.and, S.even, positive)(42), true);
+      eq(S.lift2(S.and, S.even, positive)(43), false);
+      eq(S.lift2(S.and, S.even, positive)(-42), false);
+      eq(S.lift2(S.and, S.even, positive)(-43), false);
     });
 
   });
@@ -435,6 +446,8 @@ describe('function', function() {
 
       eq(S.lift3(R.reduce, [R.add], [0], [[1, 2, 3]]), [6]);
       eq(S.lift3(R.reduce, [R.add], [0], []), []);
+
+      eq(S.lift3(R.curry(area), R.dec, S.I, R.inc)(4), 6);
     });
 
   });