Repository: plaid/sanctuary (js)

Issue:
match ?
Would it make sense to add a `match` function?

```
R.head(R.match(/xxx/, 'abcde')) // returns TypeError: Cannot read property '0' of null
```
http://bit.ly/1HasPS6

We'd be "safer" with something like
```
smatch = curry (re, s) ->
	res = R.match re, s
	return if res then Just(res) else Nothing()
```

Would it make sense to just add "safe" versions of all functions in Ramda that might otherwise need null checks? Is there some smart generic way to wrap Ramda functions and make them return `Maybe`s instead?


Pull request:
Add S.match function
Resolves #28 . I've actually been using R.compose with Sanctuary to create a safe version of `R.match` quite a few times recently so I thought I'd have a stab at a PR for this. The implementation is simple enough:

```javascript
S.match = R.curry(function (regex, s) {
    return R.compose(S.toMaybe, R.match(regex))(s);
  });
```

But I was having problems writing the tests. Obviously because `R.match` returns an array, if I do something like:

```javascript
assert(S.match(/abcd/, 'abcdefg').equals(S.Just(['abcd']))
```

it fails because `Just.equals` is checking by reference.

If I do

```javascript
assert.deepEqual(S.match(/abcd/, str), S.Just(['abcd']));
```

it fails because `match` gives back this mad arraylike object with the object keys `input` and `index` so I find myself having to do this in the setup of the tests to get everything to work:

```javascript
var str = 'abcdefg';
var search = /abcd/;
var result = ['abcd'];
result.index = 0;
result.input = 'abcdefg';
...
assert.deepEqual(S.match(search, str), S.Just(result));
...
//etc
```

It feels a bit weird to do so but I'm not really able to think of alternatives. Any help would be appreciated.

Patch:
diff --git a/index.js b/index.js
index be84f05..2841098 100644
--- a/index.js
+++ b/index.js
@@ -141,7 +141,7 @@
 
   //  Just#equals :: Maybe a -> Boolean
   Just.prototype.equals = function(maybe) {
-    return maybe instanceof Just && R.eqProps('value', maybe, this);
+    return maybe instanceof Just && maybe.value === this.value;
   };
 
   //  Just#map :: (a -> b) -> Maybe b
@@ -225,7 +225,7 @@
 
   //  Left#equals :: Either a b -> Boolean
   Left.prototype.equals = function(either) {
-    return either instanceof Left && R.eqProps('value', either, this);
+    return either instanceof Left && either.value === this.value;
   };
 
   //  Left#map :: (b -> c) -> Either a c
@@ -268,7 +268,7 @@
 
   //  Right#equals :: Either a b -> Boolean
   Right.prototype.equals = function(either) {
-    return either instanceof Right && R.eqProps('value', either, this);
+    return either instanceof Right && either.value === this.value;
   };
 
   //  Right#map :: (b -> c) -> Either a c
@@ -425,4 +425,21 @@
     return JSON.parse(s);
   });
 
+  //  regexp  ////////////////////////////////////////////////////////////////
+
+  //. match :: RegExp -> String -> Maybe [Maybe String]
+  //.
+  //. Takes a regular expression and tests it against the supplied string
+  //. returning a Nothing if there are no matches or a Just containing an
+  //. array of Maybe types corresponding to the matches found.
+  //.
+  //. ```javascript
+  //. > S.match(/abcd/, 'abcdefg')
+  //. Just([Just("abcd")])
+  //.
+  //. > S.match(/(good)?bye/, 'bye')
+  //. Just([Just("bye"), Nothing()])
+  //. ```
+  S.match = R.curry(R.compose(R.map(R.map(S.toMaybe)), S.toMaybe, R.match));
+
 }.call(this));


Test patch:
diff --git a/test/index.js b/test/index.js
index e1922d9..c2e9694 100644
--- a/test/index.js
+++ b/test/index.js
@@ -316,11 +316,6 @@ describe('maybe', function() {
       eq(just.equals(new S.Just(42)), true);
       eq(just.equals(S.Just(43)), false);
       eq(just.equals(S.Nothing()), false);
-
-      // SameValue semantics:
-      eq(S.Just(0).equals(S.Just(-0)), false);
-      eq(S.Just(-0).equals(S.Just(0)), false);
-      eq(S.Just(NaN).equals(S.Just(NaN)), true);
     });
 
     it('provides a "filter" method', function() {
@@ -598,11 +593,6 @@ describe('either', function() {
       eq(left.equals(new S.Left(42)), true);
       eq(left.equals(S.Left('42')), false);
       eq(left.equals(S.Right(42)), false);
-
-      // SameValue semantics:
-      eq(S.Left(0).equals(S.Left(-0)), false);
-      eq(S.Left(-0).equals(S.Left(0)), false);
-      eq(S.Left(NaN).equals(S.Left(NaN)), true);
     });
 
     it('provides a "map" method', function() {
@@ -748,11 +738,6 @@ describe('either', function() {
       eq(right.equals(new S.Right(42)), true);
       eq(right.equals(S.Right('42')), false);
       eq(right.equals(S.Left(42)), false);
-
-      // SameValue semantics:
-      eq(S.Right(0).equals(S.Right(-0)), false);
-      eq(S.Right(-0).equals(S.Right(0)), false);
-      eq(S.Right(NaN).equals(S.Right(NaN)), true);
     });
 
     it('provides a "map" method', function() {
@@ -1353,3 +1338,31 @@ describe('parse', function() {
   });
 
 });
+
+describe('regexp', function() {
+
+  describe('match', function() {
+
+    it('returns a Just containing an array of Justs', function() {
+      eq(S.match(/abcd/, 'abcdefg').toString(), 'Just([Just("abcd")])');
+    });
+
+    it('supports global patterns', function() {
+      eq(S.match(/[a-z]a/g, 'bananas').toString(),
+        'Just([Just("ba"), Just("na"), Just("na")])');
+    });
+
+    it('supports (optional) capturing groups', function() {
+      eq(S.match(/(good)?bye/, 'goodbye').toString(),
+        'Just([Just("goodbye"), Just("good")])');
+      eq(S.match(/(good)?bye/, 'bye').toString(),
+        'Just([Just("bye"), Nothing()])');
+    });
+
+    it('returns a Nothing() if no match', function() {
+      assert(S.match(/zzz/, 'abcdefg').equals(S.Nothing()));
+    });
+
+  });
+
+});