Repository: sanctuary-js/sanctuary
Pull request #285 (2016-11-17 02:42:18)

added S.maybe_ function
closed https://github.com/sanctuary-js/sanctuary/issues/284. Here the disscussion: how we should call
the function if Maybe known to be Nothing? If we call it without argument, then If we do:

```
S.maybe(S.K(0), R.length, S.Nothing) // it return a function because S.K still wait one more argument
``` 

The first implementation just call the function without argument, let me know if we want to update it.

davidchambers: Thanks for the pull request, @syaiful6!

> The first implementation just call the function without argument, let me know if we want to update it.

`() => <expr>` seems right to me. Were one to use `S.K(<expr>)`, `<expr>` would be eagerly evaluated. [`maybe`][1] handles the eager use case more elegantly.

```javascript
//  eager
S.maybe(<expr>, f, m)

//  lazy
S.maybe_(() => <expr>, f, m)
```

There are a couple of linting errors to address:

>     /home/ubuntu/sanctuary/test/maybe_.js
>       58:54  error  Missing semicolon                              semi
>       65:2   error  Newline required at end of file but not found  eol-last


[1]: https://sanctuary.js.org/#maybe

syaiful6: @davidchambers thanks, i updated the docs and fix the linting error. For the doctest, i am using ```factorial```, because when i am using fib, it doesn't fit with 79 max-line. Note: ``fct``` stand for factorial.
davidchambers: Thanks for the updates, @syaiful6. :)

I hope my comments don't seem pedantic. I believe it's important to maintain consistency as a project grows, particularly for a project such as this which is likely to continue to grow in size for some time.

Taking the time to mention features of doctest and other tools used in the project will hopefully make it easier to contribute to the project again in the future. :)

syaiful6: @davidchambers no worry, i am quite happy to learn these things. 👍 

Patch:
diff --git a/index.js b/index.js
index 92c2c2c..70eaea1 100644
--- a/index.js
+++ b/index.js
@@ -768,7 +768,7 @@
 
   //# on_ :: ((b, b) -> c) -> (a -> b) -> a -> a -> c
   //.
-  //. Variant of [`on`](#on) which takes an uncurried binary function.
+  //. Version of [`on`](#on) accepting uncurried functions.
   function on_(f, g, x, y) {
     return f(g(x), g(y));
   }
@@ -797,29 +797,6 @@
   }
   S.Maybe = Maybe;
 
-  //# Nothing :: Maybe a
-  //.
-  //. Nothing.
-  //.
-  //. ```javascript
-  //. > S.Nothing
-  //. Nothing
-  //. ```
-  var Nothing = S.Nothing = new Maybe(sentinel, 'Nothing');
-
-  //# Just :: a -> Maybe a
-  //.
-  //. Takes a value of any type and returns a Just with the given value.
-  //.
-  //. ```javascript
-  //. > S.Just(42)
-  //. Just(42)
-  //. ```
-  function Just(x) {
-    return new Maybe(sentinel, 'Just', x);
-  }
-  S.Just = def('Just', {}, [a, $Maybe(a)], Just);
-
   //# Maybe.empty :: -> Maybe a
   //.
   //. Returns Nothing.
@@ -872,77 +849,55 @@
   //. false
   //. ```
 
-  //# Maybe#toString :: Maybe a ~> () -> String
+  //# Maybe#ap :: Maybe (a -> b) ~> Maybe a -> Maybe b
   //.
-  //. Returns the string representation of the Maybe.
+  //. Takes a value of type `Maybe a` and returns Nothing unless `this`
+  //. is a Just *and* the argument is a Just, in which case it returns a
+  //. Just whose value is the result of of applying this Just's value to
+  //. the given Just's value.
   //.
   //. ```javascript
-  //. > S.Nothing.toString()
-  //. 'Nothing'
+  //. > S.Nothing.ap(S.Just(42))
+  //. Nothing
   //.
-  //. > S.Just([1, 2, 3]).toString()
-  //. 'Just([1, 2, 3])'
+  //. > S.Just(S.inc).ap(S.Nothing)
+  //. Nothing
+  //.
+  //. > S.Just(S.inc).ap(S.Just(42))
+  //. Just(43)
   //. ```
-  function Maybe$prototype$toString(self) {
-    return self.isJust ? 'Just(' + R.toString(self.value) + ')' : 'Nothing';
+  function Maybe$prototype$ap(self, other) {
+    return self.isJust ? other.map(self.value) : self;
   }
-  Maybe.prototype.toString =
-  method('Maybe#toString',
+  Maybe.prototype.ap =
+  method('Maybe#ap',
          {},
-         [$Maybe(a), $.String],
-         Maybe$prototype$toString);
-
-  //# Maybe#inspect :: Maybe a ~> () -> String
-  //.
-  //. Returns the string representation of the Maybe. This method is used by
-  //. `util.inspect` and the REPL to format a Maybe for display.
-  //.
-  //. See also [`Maybe#toString`](#Maybe.prototype.toString).
-  //.
-  //. ```javascript
-  //. > S.Nothing.inspect()
-  //. 'Nothing'
-  //.
-  //. > S.Just([1, 2, 3]).inspect()
-  //. 'Just([1, 2, 3])'
-  //. ```
-  Maybe.prototype.inspect = function() { return this.toString(); };
+         [$Maybe($.Function), $Maybe(a), $Maybe(b)],
+         Maybe$prototype$ap);
 
-  //# Maybe#equals :: Maybe a ~> b -> Boolean
-  //.
-  //. Takes a value of any type and returns `true` if:
-  //.
-  //.   - it is Nothing and `this` is Nothing; or
+  //# Maybe#chain :: Maybe a ~> (a -> Maybe b) -> Maybe b
   //.
-  //.   - it is a Just and `this` is a Just, and their values are equal
-  //.     according to [`R.equals`][R.equals].
+  //. Takes a function and returns `this` if `this` is Nothing; otherwise
+  //. it returns the result of applying the function to this Just's value.
   //.
   //. ```javascript
-  //. > S.Nothing.equals(S.Nothing)
-  //. true
-  //.
-  //. > S.Nothing.equals(null)
-  //. false
-  //.
-  //. > S.Just([1, 2, 3]).equals(S.Just([1, 2, 3]))
-  //. true
+  //. > S.Nothing.chain(S.parseFloat)
+  //. Nothing
   //.
-  //. > S.Just([1, 2, 3]).equals(S.Just([3, 2, 1]))
-  //. false
+  //. > S.Just('xxx').chain(S.parseFloat)
+  //. Nothing
   //.
-  //. > S.Just([1, 2, 3]).equals(S.Nothing)
-  //. false
+  //. > S.Just('12.34').chain(S.parseFloat)
+  //. Just(12.34)
   //. ```
-  function Maybe$prototype$equals(self, x) {
-    return type(x) === 'sanctuary/Maybe' &&
-           (self.isNothing ? x.isNothing
-                           : x.isJust && R.equals(x.value, self.value));
+  function Maybe$prototype$chain(self, f) {
+    return self.isJust ? f(self.value) : self;
   }
-  Maybe.prototype.equals =
-  method('Maybe#equals',
+  Maybe.prototype.chain =
+  method('Maybe#chain',
          {},
-         [$Maybe(a), b, $.Boolean],
-         Maybe$prototype$equals);
+         [$Maybe(a), $.Function, $Maybe(b)],
+         Maybe$prototype$chain);
 
   //# Maybe#concat :: Semigroup a => Maybe a ~> Maybe a -> Maybe a
   //.
@@ -993,87 +948,63 @@
   //. ```
   Maybe.prototype.empty = def('Maybe#empty', {}, [$Maybe(a)], Maybe.empty);
 
-  //# Maybe#map :: Maybe a ~> (a -> b) -> Maybe b
-  //.
-  //. Takes a function and returns `this` if `this` is Nothing; otherwise
-  //. it returns a Just whose value is the result of applying the function
-  //. to this Just's value.
+  //# Maybe#equals :: Maybe a ~> b -> Boolean
   //.
-  //. ```javascript
-  //. > S.Nothing.map(S.inc)
-  //. Nothing
+  //. Takes a value of any type and returns `true` if:
   //.
-  //. > S.Just([1, 2, 3]).map(S.sum)
-  //. Just(6)
-  //. ```
-  function Maybe$prototype$map(self, f) {
-    return self.isJust ? Just(f(self.value)) : self;
-  }
-  Maybe.prototype.map =
-  method('Maybe#map',
-         {},
-         [$Maybe(a), $.Function, $Maybe(b)],
-         Maybe$prototype$map);
-
-  //# Maybe#ap :: Maybe (a -> b) ~> Maybe a -> Maybe b
+  //.   - it is Nothing and `this` is Nothing; or
   //.
-  //. Takes a value of type `Maybe a` and returns Nothing unless `this`
-  //. is a Just *and* the argument is a Just, in which case it returns a
-  //. Just whose value is the result of of applying this Just's value to
-  //. the given Just's value.
+  //.   - it is a Just and `this` is a Just, and their values are equal
+  //.     according to [`R.equals`][R.equals].
   //.
   //. ```javascript
-  //. > S.Nothing.ap(S.Just(42))
-  //. Nothing
+  //. > S.Nothing.equals(S.Nothing)
+  //. true
   //.
-  //. > S.Just(S.inc).ap(S.Nothing)
-  //. Nothing
+  //. > S.Nothing.equals(null)
+  //. false
   //.
-  //. > S.Just(S.inc).ap(S.Just(42))
-  //. Just(43)
-  //. ```
-  function Maybe$prototype$ap(self, other) {
-    return self.isJust ? other.map(self.value) : self;
-  }
-  Maybe.prototype.ap =
-  method('Maybe#ap',
-         {},
-         [$Maybe($.Function), $Maybe(a), $Maybe(b)],
-         Maybe$prototype$ap);
-
-  //# Maybe#of :: Maybe a ~> b -> Maybe b
+  //. > S.Just([1, 2, 3]).equals(S.Just([1, 2, 3]))
+  //. true
   //.
-  //. Takes a value of any type and returns a Just with the given value.
+  //. > S.Just([1, 2, 3]).equals(S.Just([3, 2, 1]))
+  //. false
   //.
-  //. ```javascript
-  //. > S.Nothing.of(42)
-  //. Just(42)
+  //. > S.Just([1, 2, 3]).equals(S.Nothing)
+  //. false
   //. ```
-  Maybe.prototype.of = def('Maybe#of', {}, [b, $Maybe(b)], Just);
+  function Maybe$prototype$equals(self, x) {
+    return type(x) === 'sanctuary/Maybe' &&
+           (self.isNothing ? x.isNothing
+                           : x.isJust && R.equals(x.value, self.value));
+  }
+  Maybe.prototype.equals =
+  method('Maybe#equals',
+         {},
+         [$Maybe(a), b, $.Boolean],
+         Maybe$prototype$equals);
 
-  //# Maybe#chain :: Maybe a ~> (a -> Maybe b) -> Maybe b
+  //# Maybe#extend :: Maybe a ~> (Maybe a -> a) -> Maybe a
   //.
   //. Takes a function and returns `this` if `this` is Nothing; otherwise
-  //. it returns the result of applying the function to this Just's value.
+  //. it returns a Just whose value is the result of applying the function
+  //. to `this`.
   //.
   //. ```javascript
-  //. > S.Nothing.chain(S.parseFloat)
-  //. Nothing
-  //.
-  //. > S.Just('xxx').chain(S.parseFloat)
+  //. > S.Nothing.extend(x => x.value + 1)
   //. Nothing
   //.
-  //. > S.Just('12.34').chain(S.parseFloat)
-  //. Just(12.34)
+  //. > S.Just(42).extend(x => x.value + 1)
+  //. Just(43)
   //. ```
-  function Maybe$prototype$chain(self, f) {
-    return self.isJust ? f(self.value) : self;
+  function Maybe$prototype$extend(self, f) {
+    return self.isJust ? Just(f(self)) : self;
   }
-  Maybe.prototype.chain =
-  method('Maybe#chain',
+  Maybe.prototype.extend =
+  method('Maybe#extend',
          {},
-         [$Maybe(a), $.Function, $Maybe(b)],
-         Maybe$prototype$chain);
+         [$Maybe(a), $.Function, $Maybe(a)],
+         Maybe$prototype$extend);
 
   //# Maybe#filter :: Maybe a ~> (a -> Boolean) -> Maybe a
   //.
@@ -1096,6 +1027,38 @@
          [$Maybe(a), $.Function, $Maybe(a)],
          Maybe$prototype$filter);
 
+  //# Maybe#map :: Maybe a ~> (a -> b) -> Maybe b
+  //.
+  //. Takes a function and returns `this` if `this` is Nothing; otherwise
+  //. it returns a Just whose value is the result of applying the function
+  //. to this Just's value.
+  //.
+  //. ```javascript
+  //. > S.Nothing.map(S.inc)
+  //. Nothing
+  //.
+  //. > S.Just([1, 2, 3]).map(S.sum)
+  //. Just(6)
+  //. ```
+  function Maybe$prototype$map(self, f) {
+    return self.isJust ? Just(f(self.value)) : self;
+  }
+  Maybe.prototype.map =
+  method('Maybe#map',
+         {},
+         [$Maybe(a), $.Function, $Maybe(b)],
+         Maybe$prototype$map);
+
+  //# Maybe#of :: Maybe a ~> b -> Maybe b
+  //.
+  //. Takes a value of any type and returns a Just with the given value.
+  //.
+  //. ```javascript
+  //. > S.Nothing.of(42)
+  //. Just(42)
+  //. ```
+  Maybe.prototype.of = def('Maybe#of', {}, [b, $Maybe(b)], Just);
+
   //# Maybe#reduce :: Maybe a ~> ((b, a) -> b) -> b -> b
   //.
   //. Takes a function and an initial value of any type, and returns:
@@ -1148,27 +1111,64 @@
          [$Maybe(a), $.Function, b],
          Maybe$prototype$sequence);
 
-  //# Maybe#extend :: Maybe a ~> (Maybe a -> a) -> Maybe a
+  //# Maybe#toString :: Maybe a ~> () -> String
   //.
-  //. Takes a function and returns `this` if `this` is Nothing; otherwise
-  //. it returns a Just whose value is the result of applying the function
-  //. to `this`.
+  //. Returns the string representation of the Maybe.
   //.
   //. ```javascript
-  //. > S.Nothing.extend(x => x.value + 1)
-  //. Nothing
+  //. > S.Nothing.toString()
+  //. 'Nothing'
   //.
-  //. > S.Just(42).extend(x => x.value + 1)
-  //. Just(43)
+  //. > S.Just([1, 2, 3]).toString()
+  //. 'Just([1, 2, 3])'
   //. ```
-  function Maybe$prototype$extend(self, f) {
-    return self.isJust ? Just(f(self)) : self;
+  function Maybe$prototype$toString(self) {
+    return self.isJust ? 'Just(' + R.toString(self.value) + ')' : 'Nothing';
   }
-  Maybe.prototype.extend =
-  method('Maybe#extend',
+  Maybe.prototype.toString =
+  method('Maybe#toString',
          {},
-         [$Maybe(a), $.Function, $Maybe(a)],
-         Maybe$prototype$extend);
+         [$Maybe(a), $.String],
+         Maybe$prototype$toString);
+
+  //# Maybe#inspect :: Maybe a ~> () -> String
+  //.
+  //. Returns the string representation of the Maybe. This method is used by
+  //. `util.inspect` and the REPL to format a Maybe for display.
+  //.
+  //. See also [`Maybe#toString`](#Maybe.prototype.toString).
+  //.
+  //. ```javascript
+  //. > S.Nothing.inspect()
+  //. 'Nothing'
+  //.
+  //. > S.Just([1, 2, 3]).inspect()
+  //. 'Just([1, 2, 3])'
+  //. ```
+  Maybe.prototype.inspect = function() { return this.toString(); };
+
+  //# Nothing :: Maybe a
+  //.
+  //. Nothing.
+  //.
+  //. ```javascript
+  //. > S.Nothing
+  //. Nothing
+  //. ```
+  var Nothing = S.Nothing = new Maybe(sentinel, 'Nothing');
+
+  //# Just :: a -> Maybe a
+  //.
+  //. Takes a value of any type and returns a Just with the given value.
+  //.
+  //. ```javascript
+  //. > S.Just(42)
+  //. Just(42)
+  //. ```
+  function Just(x) {
+    return new Maybe(sentinel, 'Just', x);
+  }
+  S.Just = def('Just', {}, [a, $Maybe(a)], Just);
 
   //# isNothing :: Maybe a -> Boolean
   //.
@@ -1264,6 +1264,8 @@
   //. a Just, the return value is the result of applying the function to
   //. the Just's value. Otherwise, the first argument is returned.
   //.
+  //. See also [`maybe_`](#maybe_).
+  //.
   //. ```javascript
   //. > S.maybe(0, R.length, S.Just('refuge'))
   //. 6
@@ -1276,6 +1278,28 @@
   }
   S.maybe = def('maybe', {}, [b, $.Function, $Maybe(a), b], maybe);
 
+  //# maybe_ :: (() -> b) -> (a -> b) -> Maybe a -> b
+  //.
+  //. Variant of [`maybe`](#maybe) which takes a thunk so the default value
+  //. is only computed if required.
+  //.
+  //. ```javascript
+  //. > global.fib = function fib(n) {
+  //. .   return n <= 1 ? n : fib(n - 2) + fib(n - 1);
+  //. . }
+  //. fib
+  //.
+  //. > S.maybe_(() => fib(30), Math.sqrt, S.Just(1000000))
+  //. 1000
+  //.
+  //. > S.maybe_(() => fib(30), Math.sqrt, S.Nothing)
+  //. 832040
+  //. ```
+  function maybe_(f, g, maybe) {
+    return maybe.isJust ? g(maybe.value) : f();
+  }
+  S.maybe_ = def('maybe_', {}, [$.Function, $.Function, $Maybe(a), b], maybe_);
+
   //# justs :: Array (Maybe a) -> Array a
   //.
   //. Takes an array of Maybes and returns an array containing each Just's
@@ -1352,8 +1376,7 @@
 
   //# encase2_ :: ((a, b) -> c) -> a -> b -> Maybe c
   //.
-  //. Variant of [`encase2`](#encase2) which takes an uncurried binary
-  //. function.
+  //. Version of [`encase2`](#encase2) accepting uncurried functions.
   function encase2_(f, x, y) {
     try {
       return Just(f(x, y));
@@ -1375,8 +1398,7 @@
 
   //# encase3_ :: ((a, b, c) -> d) -> a -> b -> c -> Maybe d
   //.
-  //. Variant of [`encase3`](#encase3) which takes an uncurried ternary
-  //. function.
+  //. Version of [`encase3`](#encase3) accepting uncurried functions.
   function encase3_(f, x, y, z) {
     try {
       return Just(f(x, y, z));
@@ -1430,32 +1452,6 @@
   }
   S.Either = Either;
 
-  //# Left :: a -> Either a b
-  //.
-  //. Takes a value of any type and returns a Left with the given value.
-  //.
-  //. ```javascript
-  //. > S.Left('Cannot divide by zero')
-  //. Left('Cannot divide by zero')
-  //. ```
-  function Left(x) {
-    return new Either(sentinel, 'Left', x);
-  }
-  S.Left = def('Left', {}, [a, $Either(a, b)], Left);
-
-  //# Right :: b -> Either a b
-  //.
-  //. Takes a value of any type and returns a Right with the given value.
-  //.
-  //. ```javascript
-  //. > S.Right(42)
-  //. Right(42)
-  //. ```
-  function Right(x) {
-    return new Either(sentinel, 'Right', x);
-  }
-  S.Right = def('Right', {}, [b, $Either(a, b)], Right);
-
   //# Either.of :: b -> Either a b
   //.
   //. Takes a value of any type and returns a Right with the given value.
@@ -1480,87 +1476,75 @@
   //. true
   //.
   //. > S.Right(42).isLeft
-  //. false
-  //. ```
-
-  //# Either#isRight :: Either a b ~> Boolean
-  //.
-  //. `true` if `this` is a Right; `false` if `this` is a Left.
-  //.
-  //. ```javascript
-  //. > S.Right(42).isRight
-  //. true
-  //.
-  //. > S.Left('Cannot divide by zero').isRight
-  //. false
-  //. ```
-
-  //# Either#toString :: Either a b ~> () -> String
-  //.
-  //. Returns the string representation of the Either.
-  //.
-  //. ```javascript
-  //. > S.Left('Cannot divide by zero').toString()
-  //. 'Left("Cannot divide by zero")'
-  //.
-  //. > S.Right([1, 2, 3]).toString()
-  //. 'Right([1, 2, 3])'
-  //. ```
-  function Either$prototype$toString(self) {
-    return (self.isLeft ? 'Left' : 'Right') +
-           '(' + R.toString(self.value) + ')';
-  }
-  Either.prototype.toString =
-  method('Either#toString',
-         {},
-         [$Either(a, b), $.String],
-         Either$prototype$toString);
-
-  //# Either#inspect :: Either a b ~> () -> String
-  //.
-  //. Returns the string representation of the Either. This method is used by
-  //. `util.inspect` and the REPL to format a Either for display.
+  //. false
+  //. ```
+
+  //# Either#isRight :: Either a b ~> Boolean
   //.
-  //. See also [`Either#toString`](#Either.prototype.toString).
+  //. `true` if `this` is a Right; `false` if `this` is a Left.
   //.
   //. ```javascript
-  //. > S.Left('Cannot divide by zero').inspect()
-  //. 'Left("Cannot divide by zero")'
+  //. > S.Right(42).isRight
+  //. true
   //.
-  //. > S.Right([1, 2, 3]).inspect()
-  //. 'Right([1, 2, 3])'
+  //. > S.Left('Cannot divide by zero').isRight
+  //. false
   //. ```
-  Either.prototype.inspect = function() { return this.toString(); };
 
-  //# Either#equals :: Either a b ~> c -> Boolean
+  //# Either#ap :: Either a (b -> c) ~> Either a b -> Either a c
   //.
-  //. Takes a value of any type and returns `true` if:
+  //. Takes a value of type `Either a b` and returns a Left unless `this`
+  //. is a Right *and* the argument is a Right, in which case it returns
+  //. a Right whose value is the result of applying this Right's value to
+  //. the given Right's value.
   //.
-  //.   - it is a Left and `this` is a Left, and their values are equal
-  //.     according to [`R.equals`][R.equals]; or
+  //. ```javascript
+  //. > S.Left('Cannot divide by zero').ap(S.Right(42))
+  //. Left('Cannot divide by zero')
   //.
-  //.   - it is a Right and `this` is a Right, and their values are equal
-  //.     according to [`R.equals`][R.equals].
+  //. > S.Right(S.inc).ap(S.Left('Cannot divide by zero'))
+  //. Left('Cannot divide by zero')
+  //.
+  //. > S.Right(S.inc).ap(S.Right(42))
+  //. Right(43)
+  //. ```
+  function Either$prototype$ap(self, other) {
+    return self.isRight ? other.map(self.value) : self;
+  }
+  Either.prototype.ap =
+  method('Either#ap',
+         {},
+         [$Either(a, $.Function), $Either(a, b), $Either(a, c)],
+         Either$prototype$ap);
+
+  //# Either#chain :: Either a b ~> (b -> Either a c) -> Either a c
+  //.
+  //. Takes a function and returns `this` if `this` is a Left; otherwise
+  //. it returns the result of applying the function to this Right's value.
   //.
   //. ```javascript
-  //. > S.Right([1, 2, 3]).equals(S.Right([1, 2, 3]))
-  //. true
+  //. > global.sqrt = n =>
+  //. .   n < 0 ? S.Left('Cannot represent square root of negative number')
+  //. .         : S.Right(Math.sqrt(n))
+  //. sqrt
   //.
-  //. > S.Right([1, 2, 3]).equals(S.Left([1, 2, 3]))
-  //. false
+  //. > S.Left('Cannot divide by zero').chain(sqrt)
+  //. Left('Cannot divide by zero')
   //.
-  //. > S.Right(42).equals(42)
-  //. false
+  //. > S.Right(-1).chain(sqrt)
+  //. Left('Cannot represent square root of negative number')
+  //.
+  //. > S.Right(25).chain(sqrt)
+  //. Right(5)
   //. ```
-  function Either$prototype$equals(self, x) {
-    return type(x) === 'sanctuary/Either' &&
-           x.isLeft === self.isLeft && R.equals(x.value, self.value);
+  function Either$prototype$chain(self, f) {
+    return self.isRight ? f(self.value) : self;
   }
-  Either.prototype.equals =
-  method('Either#equals',
+  Either.prototype.chain =
+  method('Either#chain',
          {},
-         [$Either(a, b), c, $.Boolean],
-         Either$prototype$equals);
+         [$Either(a, b), $.Function, $Either(a, c)],
+         Either$prototype$chain);
 
   //# Either#concat :: (Semigroup a, Semigroup b) => Either a b ~> Either a b -> Either a b
   //.
@@ -1602,6 +1586,58 @@
          [$Either(a, b), $Either(a, b), $Either(a, b)],
          Either$prototype$concat);
 
+  //# Either#equals :: Either a b ~> c -> Boolean
+  //.
+  //. Takes a value of any type and returns `true` if:
+  //.
+  //.   - it is a Left and `this` is a Left, and their values are equal
+  //.     according to [`R.equals`][R.equals]; or
+  //.
+  //.   - it is a Right and `this` is a Right, and their values are equal
+  //.     according to [`R.equals`][R.equals].
+  //.
+  //. ```javascript
+  //. > S.Right([1, 2, 3]).equals(S.Right([1, 2, 3]))
+  //. true
+  //.
+  //. > S.Right([1, 2, 3]).equals(S.Left([1, 2, 3]))
+  //. false
+  //.
+  //. > S.Right(42).equals(42)
+  //. false
+  //. ```
+  function Either$prototype$equals(self, x) {
+    return type(x) === 'sanctuary/Either' &&
+           x.isLeft === self.isLeft && R.equals(x.value, self.value);
+  }
+  Either.prototype.equals =
+  method('Either#equals',
+         {},
+         [$Either(a, b), c, $.Boolean],
+         Either$prototype$equals);
+
+  //# Either#extend :: Either a b ~> (Either a b -> b) -> Either a b
+  //.
+  //. Takes a function and returns `this` if `this` is a Left; otherwise it
+  //. returns a Right whose value is the result of applying the function to
+  //. `this`.
+  //.
+  //. ```javascript
+  //. > S.Left('Cannot divide by zero').extend(x => x.value + 1)
+  //. Left('Cannot divide by zero')
+  //.
+  //. > S.Right(42).extend(x => x.value + 1)
+  //. Right(43)
+  //. ```
+  function Either$prototype$extend(self, f) {
+    return self.isLeft ? self : Right(f(self));
+  }
+  Either.prototype.extend =
+  method('Either#extend',
+         {},
+         [$Either(a, b), $.Function, $Either(a, b)],
+         Either$prototype$extend);
+
   //# Either#map :: Either a b ~> (b -> c) -> Either a c
   //.
   //. Takes a function and returns `this` if `this` is a Left; otherwise it
@@ -1624,32 +1660,6 @@
          [$Either(a, b), $.Function, $Either(a, c)],
          Either$prototype$map);
 
-  //# Either#ap :: Either a (b -> c) ~> Either a b -> Either a c
-  //.
-  //. Takes a value of type `Either a b` and returns a Left unless `this`
-  //. is a Right *and* the argument is a Right, in which case it returns
-  //. a Right whose value is the result of applying this Right's value to
-  //. the given Right's value.
-  //.
-  //. ```javascript
-  //. > S.Left('Cannot divide by zero').ap(S.Right(42))
-  //. Left('Cannot divide by zero')
-  //.
-  //. > S.Right(S.inc).ap(S.Left('Cannot divide by zero'))
-  //. Left('Cannot divide by zero')
-  //.
-  //. > S.Right(S.inc).ap(S.Right(42))
-  //. Right(43)
-  //. ```
-  function Either$prototype$ap(self, other) {
-    return self.isRight ? other.map(self.value) : self;
-  }
-  Either.prototype.ap =
-  method('Either#ap',
-         {},
-         [$Either(a, $.Function), $Either(a, b), $Either(a, c)],
-         Either$prototype$ap);
-
   //# Either#of :: Either a b ~> c -> Either a c
   //.
   //. Takes a value of any type and returns a Right with the given value.
@@ -1660,35 +1670,6 @@
   //. ```
   Either.prototype.of = def('Either#of', {}, [c, $Either(a, c)], Right);
 
-  //# Either#chain :: Either a b ~> (b -> Either a c) -> Either a c
-  //.
-  //. Takes a function and returns `this` if `this` is a Left; otherwise
-  //. it returns the result of applying the function to this Right's value.
-  //.
-  //. ```javascript
-  //. > global.sqrt = n =>
-  //. .   n < 0 ? S.Left('Cannot represent square root of negative number')
-  //. .         : S.Right(Math.sqrt(n))
-  //. sqrt
-  //.
-  //. > S.Left('Cannot divide by zero').chain(sqrt)
-  //. Left('Cannot divide by zero')
-  //.
-  //. > S.Right(-1).chain(sqrt)
-  //. Left('Cannot represent square root of negative number')
-  //.
-  //. > S.Right(25).chain(sqrt)
-  //. Right(5)
-  //. ```
-  function Either$prototype$chain(self, f) {
-    return self.isRight ? f(self.value) : self;
-  }
-  Either.prototype.chain =
-  method('Either#chain',
-         {},
-         [$Either(a, b), $.Function, $Either(a, c)],
-         Either$prototype$chain);
-
   //# Either#reduce :: Either a b ~> ((c, b) -> c) -> c -> c
   //.
   //. Takes a function and an initial value of any type, and returns:
@@ -1742,27 +1723,68 @@
          [$Either(a, b), $.Function, c],
          Either$prototype$sequence);
 
-  //# Either#extend :: Either a b ~> (Either a b -> b) -> Either a b
+  //# Either#toString :: Either a b ~> () -> String
   //.
-  //. Takes a function and returns `this` if `this` is a Left; otherwise it
-  //. returns a Right whose value is the result of applying the function to
-  //. `this`.
+  //. Returns the string representation of the Either.
   //.
   //. ```javascript
-  //. > S.Left('Cannot divide by zero').extend(x => x.value + 1)
-  //. Left('Cannot divide by zero')
+  //. > S.Left('Cannot divide by zero').toString()
+  //. 'Left("Cannot divide by zero")'
   //.
-  //. > S.Right(42).extend(x => x.value + 1)
-  //. Right(43)
+  //. > S.Right([1, 2, 3]).toString()
+  //. 'Right([1, 2, 3])'
   //. ```
-  function Either$prototype$extend(self, f) {
-    return self.isLeft ? self : Right(f(self));
+  function Either$prototype$toString(self) {
+    return (self.isLeft ? 'Left' : 'Right') +
+           '(' + R.toString(self.value) + ')';
   }
-  Either.prototype.extend =
-  method('Either#extend',
+  Either.prototype.toString =
+  method('Either#toString',
          {},
-         [$Either(a, b), $.Function, $Either(a, b)],
-         Either$prototype$extend);
+         [$Either(a, b), $.String],
+         Either$prototype$toString);
+
+  //# Either#inspect :: Either a b ~> () -> String
+  //.
+  //. Returns the string representation of the Either. This method is used by
+  //. `util.inspect` and the REPL to format a Either for display.
+  //.
+  //. See also [`Either#toString`](#Either.prototype.toString).
+  //.
+  //. ```javascript
+  //. > S.Left('Cannot divide by zero').inspect()
+  //. 'Left("Cannot divide by zero")'
+  //.
+  //. > S.Right([1, 2, 3]).inspect()
+  //. 'Right([1, 2, 3])'
+  //. ```
+  Either.prototype.inspect = function() { return this.toString(); };
+
+  //# Left :: a -> Either a b
+  //.
+  //. Takes a value of any type and returns a Left with the given value.
+  //.
+  //. ```javascript
+  //. > S.Left('Cannot divide by zero')
+  //. Left('Cannot divide by zero')
+  //. ```
+  function Left(x) {
+    return new Either(sentinel, 'Left', x);
+  }
+  S.Left = def('Left', {}, [a, $Either(a, b)], Left);
+
+  //# Right :: b -> Either a b
+  //.
+  //. Takes a value of any type and returns a Right with the given value.
+  //.
+  //. ```javascript
+  //. > S.Right(42)
+  //. Right(42)
+  //. ```
+  function Right(x) {
+    return new Either(sentinel, 'Right', x);
+  }
+  S.Right = def('Right', {}, [b, $Either(a, b)], Right);
 
   //# isLeft :: Either a b -> Boolean
   //.
@@ -1944,8 +1966,8 @@
 
   //# encaseEither2_ :: (Error -> l) -> ((a, b) -> r) -> a -> b -> Either l r
   //.
-  //. Variant of [`encaseEither2`](#encaseEither2) which takes an uncurried
-  //. binary function.
+  //. Version of [`encaseEither2`](#encaseEither2) accepting uncurried
+  //. functions.
   function encaseEither2_(f, g, x, y) {
     try {
       return Right(g(x, y));
@@ -1975,8 +1997,8 @@
 
   //# encaseEither3_ :: (Error -> l) -> ((a, b, c) -> r) -> a -> b -> c -> Either l r
   //.
-  //. Variant of [`encaseEither3`](#encaseEither3) which takes an uncurried
-  //. ternary function.
+  //. Version of [`encaseEither3`](#encaseEither3) accepting uncurried
+  //. functions.
   function encaseEither3_(f, g, x, y, z) {
     try {
       return Right(g(x, y, z));
@@ -2592,7 +2614,7 @@
 
   //# reduce_ :: Foldable f => ((a, b) -> a) -> a -> f b -> a
   //.
-  //. Variant of [`reduce`](#reduce) which takes an uncurried binary function.
+  //. Version of [`reduce`](#reduce) accepting uncurried functions.
   function reduce_(f, initial, foldable) {
     return foldable.reduce(
       type(foldable) === 'Array' ? function(acc, x) { return f(acc, x); } : f,


Test patch:
diff --git a/test/I.js b/test/I.js
index 671aab6..a0655bd 100644
--- a/test/I.js
+++ b/test/I.js
@@ -3,7 +3,6 @@
 var S = require('..');
 
 var eq = require('./internal/eq');
-var properties = require('./properties');
 
 
 describe('I', function() {
@@ -19,12 +18,4 @@ describe('I', function() {
     eq(S.I({'@@type': 'my-package/Foreign'}), {'@@type': 'my-package/Foreign'});
   });
 
-  it('is idempotent', function() {
-    eq(properties.idempotent(S.I), true);
-  });
-
-  it('is an involution', function() {
-    eq(properties.involution(S.I), true);
-  });
-
 });
diff --git a/test/maybe_.js b/test/maybe_.js
new file mode 100644
index 0000000..3b299c2
--- /dev/null
+++ b/test/maybe_.js
@@ -0,0 +1,72 @@
+'use strict';
+
+var throws = require('assert').throws;
+
+var R = require('ramda');
+
+var S = require('..');
+
+var eq = require('./internal/eq');
+var errorEq = require('./internal/errorEq');
+
+
+describe('maybe_', function() {
+
+  it('is a ternary function', function() {
+    eq(typeof S.maybe_, 'function');
+    eq(S.maybe_.length, 3);
+  });
+
+  it('type checks its arguments', function() {
+    throws(function() { S.maybe_(0, R.length); },
+           errorEq(TypeError,
+                   'Invalid value\n' +
+                   '\n' +
+                   'maybe_ :: Function -> Function -> Maybe a -> b\n' +
+                   '          ^^^^^^^^\n' +
+                   '             1\n' +
+                   '\n' +
+                   '1)  0 :: Number, FiniteNumber, Integer, ValidNumber\n' +
+                   '\n' +
+                   'The value at position 1 is not a member of ‘Function’.\n'));
+
+    throws(function() { S.maybe_(R.always(0), [1, 2, 3]); },
+           errorEq(TypeError,
+                   'Invalid value\n' +
+                   '\n' +
+                   'maybe_ :: Function -> Function -> Maybe a -> b\n' +
+                   '                      ^^^^^^^^\n' +
+                   '                         1\n' +
+                   '\n' +
+                   '1)  [1, 2, 3] :: Array Number, Array FiniteNumber, Array NonZeroFiniteNumber, Array Integer, Array ValidNumber\n' +
+                   '\n' +
+                   'The value at position 1 is not a member of ‘Function’.\n'));
+
+    throws(function() { S.maybe_(R.always(0), R.length, [1, 2, 3]); },
+           errorEq(TypeError,
+                   'Invalid value\n' +
+                   '\n' +
+                   'maybe_ :: Function -> Function -> Maybe a -> b\n' +
+                   '                                  ^^^^^^^\n' +
+                   '                                     1\n' +
+                   '\n' +
+                   '1)  [1, 2, 3] :: Array Number, Array FiniteNumber, Array NonZeroFiniteNumber, Array Integer, Array ValidNumber\n' +
+                   '\n' +
+                   'The value at position 1 is not a member of ‘Maybe a’.\n'));
+  });
+
+  it('can be applied to Nothing', function() {
+    eq(S.maybe_(R.always(0), R.length, S.Nothing), 0);
+  });
+
+  it('can be applied to a Just', function() {
+    eq(S.maybe_(R.always(0), R.length, S.Just([1, 2, 3])), 3);
+  });
+
+  it('only evaluates thunk if required', function() {
+    var count = 0;
+    eq(S.maybe_(function() { return count += 1; }, Math.sqrt, S.Just(9)), 3);
+    eq(count, 0);
+  });
+
+});
diff --git a/test/properties.js b/test/properties.js
deleted file mode 100644
index 4139816..0000000
--- a/test/properties.js
+++ /dev/null
@@ -1,21 +0,0 @@
-'use strict';
-
-var jsc = require('jsverify');
-var R = require('ramda');
-
-
-var basic = jsc.sum([jsc.integer, jsc.string, jsc.bool, jsc.falsy]);
-
-var useful = jsc.sum([basic, jsc.array(basic), jsc.dict(basic), jsc.fn(basic)]);
-
-exports.idempotent = function(f) {
-  return jsc.checkForall(useful, function(x) {
-    return R.equals(f(f(x)), f(x));
-  });
-};
-
-exports.involution = function(f) {
-  return jsc.checkForall(useful, function(x) {
-    return R.equals(f(f(x)), x);
-  });
-};