Repository: plaid/sanctuary
Pull request #151 (2016-02-06 23:08:19)

More combinators
For #139

* Add the A combinator
* Add the C combinator
* Add the B combinator
* Add the S combinator


Avaq: :information_source: I'm going to commit the changes as a result of your feedback on top. I'll neatly squash everything back down to the four commits once we're done. :)
davidchambers: Lovely work, Aldwin! At some point I'd like you to squash your commits and `git rebase master`. :)

Avaq: I'll get straight to it. Would you like them as the original four commits (one per combinator) or rather all in one?

Patch:
diff --git a/index.js b/index.js
index 390ee06..84c8153 100644
--- a/index.js
+++ b/index.js
@@ -274,6 +274,10 @@
     };
   };
 
+  var compose3 = function(f, g, x) {
+    return f(g(x));
+  };
+
   var filter = function(pred, m) {
     return m.chain(function(x) {
       return pred(x) ? m.of(x) : m.empty();
@@ -377,6 +381,81 @@
       [a, b, a],
       function(x, y) { return x; });
 
+  //# A :: (a -> b) -> a -> b
+  //.
+  //. The A combinator. Takes a function and a value, and returns the result of
+  //. applying the function to the value. Equivalent to Haskell's `($)` function.
+  //.
+  //. ```javascript
+  //. > S.A(R.inc, 1)
+  //. 2
+  //.
+  //. > R.map(S.A(R.__, 100), [R.inc, Math.sqrt])
+  //. [101, 10]
+  //. ```
+  S.A =
+  def('A',
+      {},
+      [$.Function, a, b],
+      function(f, x) { return f(x); });
+
+  //# C :: (a -> b -> c) -> b -> a -> c
+  //.
+  //. The C combinator. Takes a curried binary function and two values, and
+  //. returns the result of applying the function to the values in reverse.
+  //. Equivalent to Haskell's `flip` function.
+  //.
+  //. This function is very similar to [`flip`](#flip), except that its first
+  //. argument must be curried. This allows it to work with manually curried
+  //. functions.
+  //.
+  //. ```javascript
+  //. > S.C(R.concat, 'foo', 'bar')
+  //. 'barfoo'
+  //.
+  //. > R.filter(S.C(R.gt, 0), [-1, -2, 3, -4, 4, 2])
+  //. [3, 4, 2]
+  //. ```
+  S.C =
+  def('C',
+      {},
+      [$.Function, b, a, c],
+      function(f, x, y) { return f(y)(x); });
+
+  //# B :: (b -> c) -> (a -> b) -> a -> c
+  //.
+  //. The B combinator. Takes two functions and a value, and returns the result
+  //. of applying the first function to the result of applying the second to the
+  //. value. Equivalent to [`compose`](#compose) and Haskell's `(.)` function.
+  //.
+  //. ```javascript
+  //. > S.B(Math.sqrt, S.inc, 99)
+  //. 10
+  //. ```
+  S.B =
+  def('B',
+      {},
+      [$.Function, $.Function, a, c],
+      compose3);
+
+  //# S :: (a -> b -> c) -> (a -> b) -> a -> c
+  //.
+  //. The S combinator. Takes a curried binary function, a unary function,
+  //. and a value, and returns the result of applying the binary function to:
+  //.
+  //.   - the value; and
+  //.   - the result of applying the unary function to the value.
+  //.
+  //. ```javascript
+  //. > S.S(R.add, Math.sqrt, 100)
+  //. 110
+  //. ```
+  S.S =
+  def('S',
+      {},
+      [$.Function, $.Function, a, c],
+      function(f, g, x) { return f(x)(g(x)); });
+
   //. ### Function
 
   //# flip :: (a -> b -> c) -> b -> a -> c
@@ -386,6 +465,8 @@
   //. values. `flip` may also be applied to a Ramda-style curried
   //. function with arity greater than two.
   //.
+  //. See also [`C`](#C).
+  //.
   //. ```javascript
   //. > R.map(S.flip(Math.pow)(2), [1, 2, 3, 4, 5])
   //. [1, 4, 9, 16, 25]
@@ -466,7 +547,7 @@
   //. In general terms, `compose` performs right-to-left composition of two
   //. unary functions.
   //.
-  //. See also [`pipe`](#pipe).
+  //. See also [`B`](#B) and [`pipe`](#pipe).
   //.
   //. ```javascript
   //. > S.compose(Math.sqrt, S.inc)(99)
@@ -476,7 +557,7 @@
   def('compose',
       {},
       [$.Function, $.Function, a, c],
-      function(f, g, x) { return f(g(x)); });
+      compose3);
 
   //# pipe :: [(a -> b), (b -> c), ..., (m -> n)] -> a -> n
   //.
@@ -1022,7 +1103,7 @@
   def('toMaybe',
       {},
       [$.Any, $Maybe(a)],
-      function(x) { return x == null ? Nothing() : Just(x); });
+      R.ifElse(R.isNil, Nothing, Just));
 
   //# maybe :: b -> (a -> b) -> Maybe a -> b
   //.


Test patch:
diff --git a/test/index.js b/test/index.js
index fc2d418..8f502cd 100644
--- a/test/index.js
+++ b/test/index.js
@@ -145,6 +145,25 @@ var IdentityArb = function(arb) {
   return arb.smap(Identity, function(i) { return i.value; });
 };
 
+var runCompositionTests = function(compose) {
+
+  it('is a ternary function', function() {
+    eq(typeof compose, 'function');
+    eq(compose.length, 3);
+  });
+
+  it('composes two functions assumed to be unary', function() {
+    eq(compose(R.map(Math.sqrt), JSON.parse, '[1, 4, 9]'), [1, 2, 3]);
+  });
+
+  it('is curried', function() {
+    eq(compose(R.map(Math.sqrt)).length, 2);
+    eq(compose(R.map(Math.sqrt))(JSON.parse).length, 1);
+    eq(compose(R.map(Math.sqrt))(JSON.parse)('[1, 4, 9]'), [1, 2, 3]);
+  });
+
+};
+
 describe('invariants', function() {
 
   it('f() is equivalent to f for every "regular" function', function() {
@@ -331,6 +350,70 @@ describe('combinator', function() {
 
   });
 
+  describe('A', function() {
+
+    it('is a binary function', function() {
+      eq(typeof S.A, 'function');
+      eq(S.A.length, 2);
+    });
+
+    it('A(f, x) is equivalent to f(x)', function() {
+      eq(S.A(R.inc, 1), 2);
+      eq(R.map(S.A(R.__, 100), [R.inc, Math.sqrt]), [101, 10]);
+    });
+
+    it('is curried', function() {
+      eq(S.A(R.inc).length, 1);
+      eq(S.A(R.inc)(1), 2);
+    });
+
+  });
+
+  describe('C', function() {
+
+    it('is a ternary function', function() {
+      eq(typeof S.C, 'function');
+      eq(S.C.length, 3);
+    });
+
+    it('C(f, x, y) is equivalent to f(y)(x)', function() {
+      eq(S.C(R.concat, 'foo', 'bar'), 'barfoo');
+      eq(R.map(S.C(R.concat, '!'), ['BAM', 'POW', 'KA-POW']), ['BAM!', 'POW!', 'KA-POW!']);
+    });
+
+    it('is curried', function() {
+      eq(S.C(R.concat).length, 2);
+      eq(S.C(R.concat)('foo').length, 1);
+      eq(S.C(R.concat)('foo')('bar'), 'barfoo');
+    });
+
+  });
+
+  describe('B', function() {
+
+    runCompositionTests(S.B);
+
+  });
+
+  describe('S', function() {
+
+    it('is a ternary function', function() {
+      eq(typeof S.C, 'function');
+      eq(S.C.length, 3);
+    });
+
+    it('S(f, g, x) is equivalent to f(x)(g(x))', function() {
+      eq(S.S(R.add, Math.sqrt, 100), 110);
+    });
+
+    it('is curried', function() {
+      eq(S.S(R.add).length, 2);
+      eq(S.S(R.add)(Math.sqrt).length, 1);
+      eq(S.S(R.add)(Math.sqrt)(100), 110);
+    });
+
+  });
+
 });
 
 describe('function', function() {
@@ -459,20 +542,7 @@ describe('composition', function() {
 
   describe('compose', function() {
 
-    it('is a ternary function', function() {
-      eq(typeof S.compose, 'function');
-      eq(S.compose.length, 3);
-    });
-
-    it('composes two functions assumed to be unary', function() {
-      eq(S.compose(R.map(Math.sqrt), JSON.parse, '[1, 4, 9]'), [1, 2, 3]);
-    });
-
-    it('is curried', function() {
-      eq(S.compose(R.map(Math.sqrt)).length, 2);
-      eq(S.compose(R.map(Math.sqrt))(JSON.parse).length, 1);
-      eq(S.compose(R.map(Math.sqrt))(JSON.parse)('[1, 4, 9]'), [1, 2, 3]);
-    });
+    runCompositionTests(S.compose);
 
   });