Repository: sanctuary-js/sanctuary (js)

Issue:
Add more folds
It'd be nice to have deconstructors for more sorts of things in JS. A couple examples from discussions in the gitter room:

```
array :: x -> (a -> [a] -> x) -> [a] -> x
boolean :: x -> x -> Boolean -> x
```

Feel free to add more in the comments.

Pull request description:
array: add S.array
Closes #581 (in conjunction with #592)


Patch:
diff --git a/.eslintrc.json b/.eslintrc.json
index ef22a84..c927bb6 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -48,7 +48,7 @@
         "indent": ["off"],
         "no-eval": ["off"],
         "no-extra-semi": ["off"],
-        "no-unused-vars": ["error", {"varsIgnorePattern": "^([$]|S)$"}]
+        "no-unused-vars": ["error", {"args": "none", "varsIgnorePattern": "^([$]|S)$"}]
       }
     }
   ]
diff --git a/index.js b/index.js
index 250a44d..1287135 100644
--- a/index.js
+++ b/index.js
@@ -2924,6 +2924,36 @@
 
   //. ### Array
 
+  //# array :: b -> (a -> Array a -> b) -> Array a -> b
+  //.
+  //. Case analysis for the `Array a` type.
+  //.
+  //. ```javascript
+  //. > S.array (S.Nothing) (head => tail => S.Just (head)) ([])
+  //. Nothing
+  //.
+  //. > S.array (S.Nothing) (head => tail => S.Just (head)) ([1, 2, 3])
+  //. Just (1)
+  //.
+  //. > S.array (S.Nothing) (head => tail => S.Just (tail)) ([])
+  //. Nothing
+  //.
+  //. > S.array (S.Nothing) (head => tail => S.Just (tail)) ([1, 2, 3])
+  //. Just ([2, 3])
+  //. ```
+  function array(y) {
+    return function(f) {
+      return function(xs) {
+        return xs.length === 0 ? y : f (xs[0]) (xs.slice (1));
+      };
+    };
+  }
+  _.array = {
+    consts: {},
+    types: [b, Fn (a) (Fn ($.Array (a)) (b)), $.Array (a), b],
+    impl: array
+  };
+
   //# slice :: Integer -> Integer -> Array a -> Maybe (Array a)
   //.
   //. Takes a start index `i`, an end index `j`, and an array, and returns
@@ -3003,13 +3033,10 @@
   //. > S.head ([])
   //. Nothing
   //. ```
-  function head(xs) {
-    return xs.length > 0 ? Just (xs[0]) : Nothing;
-  }
   _.head = {
     consts: {},
     types: [$.Array (a), $Maybe (a)],
-    impl: head
+    impl: array (Nothing) (B (K) (Just))
   };
 
   //# last :: Array a -> Maybe a
@@ -3045,13 +3072,10 @@
   //. > S.tail ([])
   //. Nothing
   //. ```
-  function tail(xs) {
-    return xs.length > 0 ? Just (xs.slice (1)) : Nothing;
-  }
   _.tail = {
     consts: {},
     types: [$.Array (a), $Maybe ($.Array (a))],
-    impl: tail
+    impl: array (Nothing) (K (Just))
   };
 
   //# init :: Array a -> Maybe (Array a)


Test patch:
diff --git a/test/array.js b/test/array.js
new file mode 100644
index 0000000..c26e1b6
--- /dev/null
+++ b/test/array.js
@@ -0,0 +1,40 @@
+'use strict';
+
+var S = require ('..');
+
+var eq = require ('./internal/eq');
+
+
+test ('array', function() {
+
+  eq (typeof S.array) ('function');
+  eq (S.array.length) (1);
+  eq (S.show (S.array)) ('array :: b -> (a -> Array a -> b) -> Array a -> b');
+
+  var size = S.array (0) (function(head) {
+    return function(tail) {
+      return 1 + size (tail);
+    };
+  });
+  eq (size ([])) (0);
+  eq (size (['foo'])) (1);
+  eq (size (['foo', 'bar'])) (2);
+  eq (size (['foo', 'bar', 'baz'])) (3);
+
+  var reverse = S.array ([]) (function(head) {
+    return function(tail) {
+      return S.append (head) (reverse (tail));
+    };
+  });
+  eq (reverse ([])) ([]);
+  eq (reverse (['foo'])) (['foo']);
+  eq (reverse (['foo', 'bar'])) (['bar', 'foo']);
+  eq (reverse (['foo', 'bar', 'baz'])) (['baz', 'bar', 'foo']);
+
+  var tail = S.array (S.Nothing) (S.K (S.Just));
+  eq (tail ([])) (S.Nothing);
+  eq (tail (['foo'])) (S.Just ([]));
+  eq (tail (['foo', 'bar'])) (S.Just (['bar']));
+  eq (tail (['foo', 'bar', 'baz'])) (S.Just (['bar', 'baz']));
+
+});