Repository: plaid/sanctuary
Pull request #11 (2015-02-25 06:10:13)

maybe: implement Semigroup and Monoid
The immediate motivation for this change is to enable `filter` to be defined for Maybe as suggested by @joneshf in #10. Implementing these specifications for Maybe is a good idea regardless.



Patch:
diff --git a/index.js b/index.js
index 6ca96a9..92dcc92 100644
--- a/index.js
+++ b/index.js
@@ -46,11 +46,18 @@
     throw new Error('Cannot instantiate Maybe');
   }
 
+  //  Maybe.empty :: -> m a
+  Maybe.empty = function() {
+    return new Nothing();
+  };
+
   //  Maybe.of :: a -> m a
   Maybe.of = function(x) {
     return new Just(x);
   };
 
+  Maybe.prototype.empty = Maybe.empty;
+
   Maybe.prototype.of = Maybe.of;
 
   Maybe.prototype.type = Maybe;
@@ -72,6 +79,11 @@
     return this;
   };
 
+  //  Nothing#concat :: m a -> m a
+  Nothing.prototype.concat = function(maybe) {
+    return maybe;
+  };
+
   //  Nothing#equals :: Maybe a -> Boolean
   Nothing.prototype.equals = function(maybe) {
     return maybe instanceof Nothing;
@@ -106,6 +118,11 @@
     return f(this.value);
   };
 
+  //  Just#concat :: m a -> m a
+  Just.prototype.concat = function(maybe) {
+    return maybe instanceof Just ? Just(this.value.concat(maybe.value)) : this;
+  };
+
   //  Just#equals :: Maybe a -> Boolean
   Just.prototype.equals = function(maybe) {
     return maybe instanceof Just && maybe.value === this.value;


Test patch:
diff --git a/test/index.js b/test/index.js
index cf45aca..a8760ff 100644
--- a/test/index.js
+++ b/test/index.js
@@ -71,6 +71,13 @@ describe('maybe', function() {
       eq(nothing.chain(S.head), nothing);
     });
 
+    it('provides a "concat" method', function() {
+      var nothing = S.Nothing();
+      eq(nothing.concat.length, 1);
+      assert(nothing.concat(S.Nothing()).equals(S.Nothing()));
+      assert(nothing.concat(S.Just('foo')).equals(S.Just('foo')));
+    });
+
     it('provides an "equals" method', function() {
       var nothing = S.Nothing();
       eq(nothing.equals.length, 1);
@@ -94,6 +101,25 @@ describe('maybe', function() {
       eq(S.Nothing().or(just), just);
     });
 
+    it('implements Semigroup', function() {
+      var a = S.Nothing();
+      var b = S.Nothing();
+      var c = S.Nothing();
+
+      // associativity
+      assert(a.concat(b).concat(c).equals(a.concat(b.concat(c))));
+    });
+
+    it('implements Monoid', function() {
+      var a = S.Nothing();
+
+      // left identity
+      assert(a.empty().concat(a).equals(a));
+
+      // right identity
+      assert(a.concat(a.empty()).equals(a));
+    });
+
     it('implements Functor', function() {
       var a = S.Nothing();
       var f = inc;
@@ -195,6 +221,13 @@ describe('maybe', function() {
       assert(just.chain(S.head).equals(S.Just(1)));
     });
 
+    it('provides a "concat" method', function() {
+      var just = S.Just('foo');
+      eq(just.concat.length, 1);
+      assert(just.concat(S.Nothing()).equals(S.Just('foo')));
+      assert(just.concat(S.Just('bar')).equals(S.Just('foobar')));
+    });
+
     it('provides an "equals" method', function() {
       var just = S.Just(42);
       eq(just.equals.length, 1);
@@ -218,6 +251,25 @@ describe('maybe', function() {
       eq(just.or(S.Just(42)), just);
     });
 
+    it('implements Semigroup', function() {
+      var a = S.Just('foo');
+      var b = S.Just('bar');
+      var c = S.Just('baz');
+
+      // associativity
+      assert(a.concat(b).concat(c).equals(a.concat(b.concat(c))));
+    });
+
+    it('implements Monoid', function() {
+      var a = S.Just([1, 2, 3]);
+
+      // left identity
+      assert(a.empty().concat(a).equals(a));
+
+      // right identity
+      assert(a.concat(a.empty()).equals(a));
+    });
+
     it('implements Functor', function() {
       var a = S.Just(7);
       var f = inc;