Repository: sanctuary-js/sanctuary (js)

Issue:
Add pipeK for monads
Ramda has Kleisli composition via [composeK](http://ramdajs.com/docs/#composeK) and [pipeK](http://ramdajs.com/docs/#pipeK). We probably only need one of those.

It is worth considering instead a specification for Categories as suggested in https://github.com/fantasyland/fantasy-land/issues/208 and implementing a Kleisli type that exposes its own `compose`.

This would mean `pipe` would be sufficient if it were implemented in terms of `compose`.

Pull request description:
composition: add pipeK
Closes #380 

Patch:
diff --git a/index.js b/index.js
index 4c931de..882159c 100644
--- a/index.js
+++ b/index.js
@@ -1489,9 +1489,9 @@
 
   //# pipe :: Foldable f => f (Any -> Any) -> a -> b
   //.
-  //. Takes an array of functions assumed to be unary and a value of any type,
-  //. and returns the result of applying the sequence of transformations to
-  //. the initial value.
+  //. Takes a sequence of functions assumed to be unary and a value of any
+  //. type, and returns the result of applying the sequence of transformations
+  //. to the initial value.
   //.
   //. In general terms, `pipe` performs left-to-right composition of a sequence
   //. of functions. `pipe([f, g, h], x)` is equivalent to `h(g(f(x)))`.
@@ -1505,6 +1505,29 @@
   }
   S.pipe = def('pipe', {f: [Z.Foldable]}, [f(Fn($.Any, $.Any)), a, b], pipe);
 
+  //# pipeK :: (Foldable f, Chain m) => f (Any -> m Any) -> m a -> m b
+  //.
+  //. Takes a sequence of functions assumed to be unary which return values
+  //. with a [Chain][], and a value of that Chain, and returns the result
+  //. of applying the sequence of transformations to the initial value.
+  //.
+  //. In general terms, `pipeK` performs left-to-right [Kleisli][] composition
+  //. of an sequence of functions. `pipeK([f, g, h], x)` is equivalent to
+  //. `chain(h, chain(g, chain(f, x)))`.
+  //.
+  //. ```javascript
+  //. > S.pipeK([S.tail, S.tail, S.head], S.Just([1, 2, 3, 4]))
+  //. Just(3)
+  //. ```
+  function pipeK(fs, x) {
+    return Z.reduce(function(x, f) { return Z.chain(f, x); }, x, fs);
+  }
+  S.pipeK =
+  def('pipeK',
+      {f: [Z.Foldable], m: [Z.Chain]},
+      [f(Fn($.Any, m($.Any))), m(a), m(b)],
+      pipeK);
+
   //# on :: (b -> b -> c) -> (a -> b) -> a -> a -> c
   //.
   //. Takes a binary function `f`, a unary function `g`, and two
@@ -4636,11 +4659,13 @@
 //. [Apply]:            v:fantasyland/fantasy-land#apply
 //. [Bifunctor]:        v:fantasyland/fantasy-land#bifunctor
 //. [BinaryType]:       v:sanctuary-js/sanctuary-def#BinaryType
+//. [Chain]:            v:fantasyland/fantasy-land#chain
 //. [Either]:           #either-type
 //. [Extend]:           v:fantasyland/fantasy-land#extend
 //. [Fantasy Land]:     v:fantasyland/fantasy-land
 //. [Foldable]:         v:fantasyland/fantasy-land#foldable
 //. [Haskell]:          https://www.haskell.org/
+//. [Kleisli]:          https://en.wikipedia.org/wiki/Kleisli_category
 //. [Maybe]:            #maybe-type
 //. [Monad]:            v:fantasyland/fantasy-land#monad
 //. [Monoid]:           v:fantasyland/fantasy-land#monoid


Test patch:
diff --git a/test/pipeK.js b/test/pipeK.js
new file mode 100644
index 0000000..469808d
--- /dev/null
+++ b/test/pipeK.js
@@ -0,0 +1,29 @@
+'use strict';
+
+var S = require('./internal/sanctuary');
+
+var List = require('./internal/List');
+var eq = require('./internal/eq');
+
+
+var Cons = List.Cons;
+var Nil = List.Nil;
+
+
+test('pipeK', function() {
+
+  eq(typeof S.pipeK, 'function');
+  eq(S.pipeK.length, 2);
+  eq(S.pipeK.toString(), 'pipeK :: (Foldable f, Chain m) => f (Any -> m Any) -> m a -> m b');
+
+  eq(S.pipeK([], S.Just([1, 2, 3])), S.Just([1, 2, 3]));
+  eq(S.pipeK([S.tail], S.Just([1, 2, 3])), S.Just([2, 3]));
+  eq(S.pipeK([S.tail, S.tail], S.Just([1, 2, 3])), S.Just([3]));
+  eq(S.pipeK([S.tail, S.tail, S.head], S.Just([1, 2, 3])), S.Just(3));
+
+  eq(S.pipeK(Nil, S.Just([1, 2, 3])), S.Just([1, 2, 3]));
+  eq(S.pipeK(Cons(S.tail, Nil), S.Just([1, 2, 3])), S.Just([2, 3]));
+  eq(S.pipeK(Cons(S.tail, Cons(S.tail, Nil)), S.Just([1, 2, 3])), S.Just([3]));
+  eq(S.pipeK(Cons(S.tail, Cons(S.tail, Cons(S.head, Nil))), S.Just([1, 2, 3])), S.Just(3));
+
+});