Skip to content

Commit

Permalink
expand documentation of type signatures and fix inconsistencies
Browse files Browse the repository at this point in the history
  • Loading branch information
davidchambers committed Sep 27, 2016
1 parent d9d0259 commit 292a3ea
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 31 deletions.
103 changes: 72 additions & 31 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,59 @@
//. ## Types
//.
//. Sanctuary uses Haskell-like type signatures to describe the types of
//. values, including functions. `'foo'`, for example, has type `String`;
//. `[1, 2, 3]` has type `Array Number`. The arrow (`->`) is used to express
//. a function's type. `Math.abs`, for example, has type `Number -> Number`.
//. That is, it takes an argument of type `Number` and returns a value of
//. type `Number`.
//. values, including functions. `'foo'`, for example, is a member of `String`;
//. `[1, 2, 3]` is a member of `Array Number`. The double colon (`::`) is used
//. to mean "is a member of", so one could write:
//.
//. [`R.map`][R.map] has type `(a -> b) -> Array a -> Array b`. That is,
//. it takes an argument of type `a -> b` and returns a value of type
//. `Array a -> Array b`. `a` and `b` are type variables: applying `R.map`
//. to a value of type `String -> Number` will result in a value of type
//. `Array String -> Array Number`.
//. 'foo' :: String
//. [1, 2, 3] :: Array Number
//.
//. An identifier may appear to the left of the double colon:
//.
//. Math.PI :: Number
//.
//. The arrow (`->`) is used to express a function's type:
//.
//. Math.abs :: Number -> Number
//.
//. That states that `Math.abs` is a unary function which takes an argument
//. of type `Number` and returns a value of type `Number`.
//.
//. Some functions are parametrically polymorphic: their types are not fixed.
//. Type variables are used in the representations of such functions:
//.
//. S.I :: a -> a
//.
//. `a` is a type variable. Type variables are not capitalized, so they
//. are differentiable from type identifiers (which are always capitalized).
//. By convention type variables have single-character names. The signature
//. above states that `S.I` takes a value of any type and returns a value of
//. the same type. Some signatures feature multiple type variables:
//.
//. S.K :: a -> b -> a
//.
//. It must be possible to replace all occurrences of `a` with a concrete type.
//. The same applies for each other type variable. For the function above, the
//. types with which `a` and `b` are replaced may be different, but needn't be.
//.
//. Since all Sanctuary functions are curried (they accept their arguments
//. one at a time), a binary function is represented as a unary function which
//. returns a unary function: `* -> * -> *`. This aligns neatly with Haskell,
//. which uses curried functions exclusively. In JavaScript, though, we may
//. wish to represent the types of functions with arities less than or greater
//. than one. The general form is `(<input-types>) -> <output-type>`, where
//. `<input-types>` comprises zero or more comma–space (<code>, </code>)
//. -separated type representations:
//.
//. - `() -> String`
//. - `(a, b) -> a`
//. - `(a, b, c) -> d`
//.
//. `Number -> Number` can thus be seen as shorthand for `(Number) -> Number`.
//.
//. The question mark (`?`) is used to represent types which include `null`
//. and `undefined` as members. `String?`, for example, represents the type
//. comprising `null`, `undefined`, and all strings.
//.
//. Sanctuary embraces types. JavaScript doesn't support algebraic data types,
//. but these can be simulated by providing a group of data constructors which
Expand All @@ -61,6 +103,10 @@
//. (for any type `a`) with an argument of type `a -> b` (for any type `b`),
//. it returns a value of type `Maybe b`._
//.
//. The squiggly arrow is also used when representing non-function properties.
//. `Maybe a ~> Boolean`, for example, represents a Boolean property of a value
//. of type `Maybe a`.
//.
//. Sanctuary supports type classes: constraints on type variables. Whereas
//. `a -> a` implicitly supports every type, `Functor f => (a -> b) -> f a ->
//. f b` requires that `f` be a type which satisfies the requirements of the
Expand Down Expand Up @@ -182,25 +228,20 @@
return f(g(x));
};

// filter :: (Monad m, Monoid m) => ((a -> Boolean), m a) -> m a
// filter :: (Monad m, Monoid (m a)) => (a -> Boolean, m a) -> m a
var filter = function(pred, m) {
return m.chain(function(x) {
return pred(x) ? m.of(x) : m.empty();
});
};

// hasMethod :: String -> Any -> Boolean
// hasMethod :: String -> a -> Boolean
var hasMethod = function(name) {
return function(x) {
return x != null && typeof x[name] === 'function';
};
};

// inspect :: -> String
var inspect = /* istanbul ignore next */ function() {
return this.toString();
};

// negativeZero :: a -> Boolean
var negativeZero = R.either(R.equals(-0), R.equals(new Number(-0)));

Expand Down Expand Up @@ -835,12 +876,12 @@
[a, $Maybe(a)],
function(x) { return Just(x); });

//# Maybe#@@type :: String
//# Maybe#@@type :: Maybe a ~> String
//.
//. Maybe type identifier, `'sanctuary/Maybe'`.
Maybe.prototype['@@type'] = 'sanctuary/Maybe';

//# Maybe#isNothing :: Boolean
//# Maybe#isNothing :: Maybe a ~> Boolean
//.
//. `true` if `this` is Nothing; `false` if `this` is a Just.
//.
Expand All @@ -852,7 +893,7 @@
//. false
//. ```

//# Maybe#isJust :: Boolean
//# Maybe#isJust :: Maybe a ~> Boolean
//.
//. `true` if `this` is a Just; `false` if `this` is Nothing.
//.
Expand Down Expand Up @@ -1116,7 +1157,7 @@
return maybe.isJust ? R.map(Just, maybe.value) : of(maybe);
});

//# Maybe#toBoolean :: Maybe a ~> Boolean
//# Maybe#toBoolean :: Maybe a ~> () -> Boolean
//.
//. Returns `false` if `this` is Nothing; `true` if `this` is a Just.
//.
Expand All @@ -1133,7 +1174,7 @@
[$Maybe(a), $.Boolean],
prop('isJust'));

//# Maybe#toString :: Maybe a ~> String
//# Maybe#toString :: Maybe a ~> () -> String
//.
//. Returns the string representation of the Maybe.
//.
Expand All @@ -1153,7 +1194,7 @@
: 'Nothing';
});

//# Maybe#inspect :: Maybe a ~> String
//# 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.
Expand All @@ -1167,7 +1208,7 @@
//. > S.Just([1, 2, 3]).inspect()
//. 'Just([1, 2, 3])'
//. ```
Maybe.prototype.inspect = inspect;
Maybe.prototype.inspect = function() { return this.toString(); };

//# Nothing :: Maybe a
//.
Expand Down Expand Up @@ -1491,12 +1532,12 @@
[b, $Either(a, b)],
function(x) { return Right(x); });

//# Either#@@type :: String
//# Either#@@type :: Either a b ~> String
//.
//. Either type identifier, `'sanctuary/Either'`.
Either.prototype['@@type'] = 'sanctuary/Either';

//# Either#isLeft :: Boolean
//# Either#isLeft :: Either a b ~> Boolean
//.
//. `true` if `this` is a Left; `false` if `this` is a Right.
//.
Expand All @@ -1508,7 +1549,7 @@
//. false
//. ```

//# Either#isRight :: Boolean
//# Either#isRight :: Either a b ~> Boolean
//.
//. `true` if `this` is a Right; `false` if `this` is a Left.
//.
Expand Down Expand Up @@ -1746,7 +1787,7 @@
return either.isRight ? R.map(Right, either.value) : of(either);
});

//# Either#toBoolean :: Either a b ~> Boolean
//# Either#toBoolean :: Either a b ~> () -> Boolean
//.
//. Returns `false` if `this` is a Left; `true` if `this` is a Right.
//.
Expand All @@ -1763,7 +1804,7 @@
[$Either(a, b), $.Boolean],
prop('isRight'));

//# Either#toString :: Either a b ~> String
//# Either#toString :: Either a b ~> () -> String
//.
//. Returns the string representation of the Either.
//.
Expand All @@ -1783,7 +1824,7 @@
'(' + R.toString(either.value) + ')';
});

//# Either#inspect :: Either a b ~> String
//# 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.
Expand All @@ -1797,7 +1838,7 @@
//. > S.Right([1, 2, 3]).inspect()
//. 'Right([1, 2, 3])'
//. ```
Either.prototype.inspect = inspect;
Either.prototype.inspect = function() { return this.toString(); };

//# Left :: a -> Either a b
//.
Expand Down
5 changes: 5 additions & 0 deletions test/Either/Left.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,11 @@ describe('Left', function() {
eq(S.Left('abc').toString(), 'Left("abc")');
});

it('provides an "inspect" method', function() {
eq(S.Left('abc').inspect.length, 0);
eq(S.Left('abc').inspect(), 'Left("abc")');
});

it('implements Semigroup', function() {
var a = S.Left('foo');
var b = S.Left('bar');
Expand Down
5 changes: 5 additions & 0 deletions test/Either/Right.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,11 @@ describe('Right', function() {
eq(S.Right([1, 2, 3]).toString(), 'Right([1, 2, 3])');
});

it('provides an "inspect" method', function() {
eq(S.Right([1, 2, 3]).inspect.length, 0);
eq(S.Right([1, 2, 3]).inspect(), 'Right([1, 2, 3])');
});

it('implements Semigroup', function() {
var a = S.Right('foo');
var b = S.Right('bar');
Expand Down
5 changes: 5 additions & 0 deletions test/Maybe/Just.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,11 @@ describe('Just', function() {
eq(S.Just([1, 2, 3]).toString(), 'Just([1, 2, 3])');
});

it('provides an "inspect" method', function() {
eq(S.Just([1, 2, 3]).inspect.length, 0);
eq(S.Just([1, 2, 3]).inspect(), 'Just([1, 2, 3])');
});

it('implements Semigroup', function() {
var a = S.Just('foo');
var b = S.Just('bar');
Expand Down
5 changes: 5 additions & 0 deletions test/Maybe/Nothing.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@ describe('Nothing', function() {
eq(S.Nothing.toString(), 'Nothing');
});

it('provides an "inspect" method', function() {
eq(S.Nothing.inspect.length, 0);
eq(S.Nothing.inspect(), 'Nothing');
});

it('implements Semigroup', function() {
var a = S.Nothing;
var b = S.Nothing;
Expand Down

0 comments on commit 292a3ea

Please sign in to comment.