Repository: sanctuary-js/sanctuary (js)

Issue:
curry2 :: ((a, b) -> c) -> a -> b -> c
Now that Sanctuary is close to being an alternative to Ramda rather than a library intended to be used alongside it, we should consider which Ramda functions we would miss were we to remove Ramda from our projects. [`R.curry`][1] and [`R.curryN`][2] are certainly important functions. [`def`][3] performs currying, of course, but not every project which depends on Sanctuary also depends on sanctuary-def.

The type signatures of these Ramda functions are misleading:

```haskell
curry  :: (* -> a) -> (* -> a)
curryN :: Number -> (* -> a) -> (* -> a)
```

Accurate type signatures are an important tool for reasoning about functions. The Sanctuary functions would be much simpler:

```haskell
curry2 :: ((a, b) -> c) -> a -> b -> c
curry3 :: ((a, b, c) -> d) -> a -> b -> c -> d
curry4 :: ((a, b, c, d) -> e) -> a -> b -> c -> d -> e
curry5 :: ((a, b, c, d, e) -> f) -> a -> b -> c -> d -> e -> f
```

We must choose the maximum supported arity. I don't think it's necessary to go all the way to `curry5`; `curry2` and `curry3` should suffice. If you call recall using `R.curry` or `R.curryN` to produce a function of arity greater than 3, please speak up.

:curry:


[1]: http://ramdajs.com/0.22.1/docs/#curry
[2]: http://ramdajs.com/0.22.1/docs/#curryN
[3]: https://github.com/sanctuary-js/sanctuary-def


Pull request description:
Curry: Add functions curry2 through curry5
Hi, first pull request here. Thanks for the hand-holding. This closes #277. 
Things not complete or I'm not sure about: 
- [x] Examples for curry3 - 5. 
  Looks like all of the examples in the readme are one liners. 
- [x] Documentation text is probably too short, but I'm not the one that should be explaining currying to anyone. 
- [x] I probably skipped some test cases. Is there an S.__ or similar that needs to be tested? 
- [x] The definition for`var e = $.TypeVariable('e');` is skipped and I don't know why. So curry4 and 5 don't have an 'e': `S.curry4 = def('curry4', {}, [$.Function, a, b, c, d, f], curry4);`
- [x] Not sure what variable names people traditionally use for functions of large arity. 
  I have `function(v, w, x, y, z)`...
- [x] Should I `make` the README and commit, or does that happen later? 


Patch:
diff --git a/index.js b/index.js
index be57965..afcd6f8 100644
--- a/index.js
+++ b/index.js
@@ -300,6 +300,7 @@
   var b = $.TypeVariable('b');
   var c = $.TypeVariable('c');
   var d = $.TypeVariable('d');
+  var e = $.TypeVariable('e');
   var f = $.TypeVariable('f');
   var l = $.TypeVariable('l');
   var r = $.TypeVariable('r');
@@ -632,6 +633,89 @@
 
   //. ### Function
 
+  //# curry2 :: ((a, b) -> c) -> a -> b -> c
+  //.
+  //. Curries the given binary function.
+  //.
+  //. ```javascript
+  //. > R.map(S.curry2(Math.pow)(10), [1, 2, 3])
+  //. [10, 100, 1000]
+  //.
+  //. > R.map(S.curry2(Math.pow, 10), [1, 2, 3])
+  //. [10, 100, 1000]
+  //. ```
+  function curry2(f, x, y) {
+    return f(x, y);
+  }
+  S.curry2 = def('curry2', {}, [$.Function, a, b, c], curry2);
+
+  //# curry3 :: ((a, b, c) -> d) -> a -> b -> c -> d
+  //.
+  //. Curries the given ternary function.
+  //.
+  //. ```javascript
+  //. > global.replaceString = S.curry3((what, replacement, string) =>
+  //. .   string.replace(what, replacement)
+  //. . )
+  //. replaceString
+  //.
+  //. > replaceString('banana')('orange')('banana icecream')
+  //. 'orange icecream'
+  //.
+  //. > replaceString('banana', 'orange', 'banana icecream')
+  //. 'orange icecream'
+  //. ```
+  function curry3(f, x, y, z) {
+    return f(x, y, z);
+  }
+  S.curry3 = def('curry3', {}, [$.Function, a, b, c, d], curry3);
+
+  //# curry4 :: ((a, b, c, d) -> e) -> a -> b -> c -> d -> e
+  //.
+  //. Curries the given quaternary function.
+  //.
+  //. ```javascript
+  //. > global.createRect = S.curry4((x, y, width, height) =>
+  //. .   ({x, y, width, height})
+  //. . )
+  //. createRect
+  //.
+  //. > createRect(0)(0)(10)(10)
+  //. {x: 0, y: 0, width: 10, height: 10}
+  //.
+  //. > createRect(0, 0, 10, 10)
+  //. {x: 0, y: 0, width: 10, height: 10}
+  //. ```
+  function curry4(f, w, x, y, z) {
+    return f(w, x, y, z);
+  }
+  S.curry4 = def('curry4', {}, [$.Function, a, b, c, d, e], curry4);
+
+  //# curry5 :: ((a, b, c, d, e) -> f) -> a -> b -> c -> d -> e -> f
+  //.
+  //. Curries the given quinary function.
+  //.
+  //. ```javascript
+  //. > global.toUrl = S.curry5((protocol, creds, hostname, port, pathname) =>
+  //. .   protocol + '//' +
+  //. .   S.maybe('', _ => _.username + ':' + _.password + '@', creds) +
+  //. .   hostname +
+  //. .   S.maybe('', S.concat(':'), port) +
+  //. .   pathname
+  //. . )
+  //. toUrl
+  //.
+  //. > toUrl('https:')(S.Nothing)('example.com')(S.Just('443'))('/foo/bar')
+  //. 'https://example.com:443/foo/bar'
+  //.
+  //. > toUrl('https:', S.Nothing, 'example.com', S.Just('443'), '/foo/bar')
+  //. 'https://example.com:443/foo/bar'
+  //. ```
+  function curry5(f, v, w, x, y, z) {
+    return f(v, w, x, y, z);
+  }
+  S.curry5 = def('curry5', {}, [$.Function, a, b, c, d, e, r], curry5);
+
   //# flip :: ((a, b) -> c) -> b -> a -> c
   //.
   //. Takes a binary function and two values, and returns the result of


Test patch:
diff --git a/test/curry2.js b/test/curry2.js
new file mode 100644
index 0000000..5c2a95a
--- /dev/null
+++ b/test/curry2.js
@@ -0,0 +1,17 @@
+'use strict';
+
+var S = require('..');
+
+var eq = require('./internal/eq');
+
+
+test('curry2', function() {
+
+  eq(typeof S.curry2, 'function');
+  eq(S.curry2.length, 3);
+
+  var curried = S.curry2(function(x, y) { return x + y; });
+  eq(curried(1, 2), 3);
+  eq(curried(1)(2), 3);
+
+});
diff --git a/test/curry3.js b/test/curry3.js
new file mode 100644
index 0000000..f9ba80f
--- /dev/null
+++ b/test/curry3.js
@@ -0,0 +1,19 @@
+'use strict';
+
+var S = require('..');
+
+var eq = require('./internal/eq');
+
+
+test('curry3', function() {
+
+  eq(typeof S.curry3, 'function');
+  eq(S.curry3.length, 4);
+
+  var curried = S.curry3(function(x, y, z) { return x + y + z; });
+  eq(curried(1, 2, 3), 6);
+  eq(curried(1, 2)(3), 6);
+  eq(curried(1)(2, 3), 6);
+  eq(curried(1)(2)(3), 6);
+
+});
diff --git a/test/curry4.js b/test/curry4.js
new file mode 100644
index 0000000..ec12b87
--- /dev/null
+++ b/test/curry4.js
@@ -0,0 +1,23 @@
+'use strict';
+
+var S = require('..');
+
+var eq = require('./internal/eq');
+
+
+test('curry4', function() {
+
+  eq(typeof S.curry4, 'function');
+  eq(S.curry4.length, 5);
+
+  var curried = S.curry4(function(w, x, y, z) { return w + x + y + z; });
+  eq(curried(1, 2, 3, 4), 10);
+  eq(curried(1, 2, 3)(4), 10);
+  eq(curried(1, 2)(3, 4), 10);
+  eq(curried(1, 2)(3)(4), 10);
+  eq(curried(1)(2, 3, 4), 10);
+  eq(curried(1)(2, 3)(4), 10);
+  eq(curried(1)(2)(3, 4), 10);
+  eq(curried(1)(2)(3)(4), 10);
+
+});
diff --git a/test/curry5.js b/test/curry5.js
new file mode 100644
index 0000000..0a5a823
--- /dev/null
+++ b/test/curry5.js
@@ -0,0 +1,31 @@
+'use strict';
+
+var S = require('..');
+
+var eq = require('./internal/eq');
+
+
+test('curry5', function() {
+
+  eq(typeof S.curry5, 'function');
+  eq(S.curry5.length, 6);
+
+  var curried = S.curry5(function(v, w, x, y, z) { return v + w + x + y + z; });
+  eq(curried(1, 2, 3, 4, 5), 15);
+  eq(curried(1, 2, 3, 4)(5), 15);
+  eq(curried(1, 2, 3)(4, 5), 15);
+  eq(curried(1, 2, 3)(4)(5), 15);
+  eq(curried(1, 2)(3, 4, 5), 15);
+  eq(curried(1, 2)(3, 4)(5), 15);
+  eq(curried(1, 2)(3)(4, 5), 15);
+  eq(curried(1, 2)(3)(4)(5), 15);
+  eq(curried(1)(2, 3, 4, 5), 15);
+  eq(curried(1)(2, 3, 4)(5), 15);
+  eq(curried(1)(2, 3)(4, 5), 15);
+  eq(curried(1)(2, 3)(4)(5), 15);
+  eq(curried(1)(2)(3, 4, 5), 15);
+  eq(curried(1)(2)(3, 4)(5), 15);
+  eq(curried(1)(2)(3)(4, 5), 15);
+  eq(curried(1)(2)(3)(4)(5), 15);
+
+});