diff --git a/bower.json b/bower.json index 7d01e7a0..0c7d6825 100644 --- a/bower.json +++ b/bower.json @@ -25,7 +25,7 @@ ], "dependencies": { "ramda": "0.19.x", - "sanctuary-def": "0.3.x" + "sanctuary-def": "0.4.x" }, "ignore": [ "/.git/", diff --git a/index.js b/index.js index f9b22a91..ea85cd6b 100644 --- a/index.js +++ b/index.js @@ -102,6 +102,44 @@ //. //. `Number` is the sole inhabitant of the TypeRep Number type. //. +//. ## Type checking +//. +//. Sanctuary functions are defined via [sanctuary-def][] to provide run-time +//. type checking. This is tremendously useful during development: type errors +//. are reported immediately, avoiding circuitous stack traces (at best) and +//. silent failures due to type coercion (at worst). For example: +//. +//. ```javascript +//. S.inc('XXX'); +//. // ! TypeError: ‘inc’ expected a value of type FiniteNumber as its first argument; received "XXX" +//. ``` +//. +//. Compare this to the behaviour of Ramda's unchecked equivalent: +//. +//. ```javascript +//. R.inc('XXX'); +//. // => '1XXX' +//. ``` +//. +//. There is a performance cost to run-time type checking. One may wish to +//. disable type checking in certain contexts to avoid paying this cost. +//. There are actually two versions of the Sanctuary module: one with type +//. checking; one without. The latter is accessible via the `unchecked` +//. property of the former. +//. +//. When application of `S.unchecked.` honours the function's type +//. signature the result will be the same as if `S.` had been used +//. instead. Otherwise, the behaviour is unspecified. +//. +//. In Node, one could use an environment variable to determine which version +//. of the Sanctuary module to use: +//. +//. ```javascript +//. const S = process.env.NODE_ENV === 'production' ? +//. require('sanctuary').unchecked : +//. require('sanctuary'); +//. ``` +//. //. ## API /* global define, self */ @@ -123,12 +161,35 @@ 'use strict'; - var S = {}; - var _ = R.__; var sentinel = {}; + // _type :: a -> String + var _type = function(x) { + return x != null && R.type(x['@@type']) === 'String' ? x['@@type'] + : R.type(x); + }; + + // compose2 :: ((b -> c), (a -> b)) -> a -> c + var compose2 = function(f, g) { + return function(x) { + return f(g(x)); + }; + }; + + // compose3 :: ((b -> c), (a -> b), a) -> c + var compose3 = function(f, g, x) { + return f(g(x)); + }; + + // filter :: (Monad m, Monoid m) => ((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 var hasMethod = function(name) { return function(x) { @@ -136,6 +197,15 @@ }; }; + // 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))); // jshint ignore:line + // Accessible :: TypeClass var Accessible = $.TypeClass( 'sanctuary/Accessible', @@ -197,7 +267,7 @@ var r = $.TypeVariable('r'); // $Either :: Type -> Type -> Type - var $Either = S.EitherType = $.BinaryType( + var $Either = $.BinaryType( 'sanctuary/Either', function(x) { return x != null && x['@@type'] === 'sanctuary/Either'; }, function(either) { return either.isLeft ? [either.value] : []; }, @@ -219,7 +289,7 @@ ); // $Maybe :: Type -> Type - var $Maybe = S.MaybeType = $.UnaryType( + var $Maybe = $.UnaryType( 'sanctuary/Maybe', function(x) { return x != null && x['@@type'] === 'sanctuary/Maybe'; }, function(maybe) { return maybe.isJust ? [maybe.value] : []; } @@ -250,7 +320,14 @@ $.ValidNumber ]); - var def = $.create(env); + // createSanctuary :: Boolean -> Module + var createSanctuary = function(checkTypes) { + + // To avoid excessive indentation this function's body is not indented. + + var S = {EitherType: $Either, MaybeType: $Maybe}; + + var def = $.create(checkTypes, env); var method = function(name, constraints, types, _f) { var f = def(name, constraints, types, _f); @@ -259,36 +336,6 @@ }); }; - // _type :: a -> String - var _type = function(x) { - return x != null && R.type(x['@@type']) === 'String' ? x['@@type'] - : R.type(x); - }; - - var compose2 = function(f, g) { - return function(x) { - return f(g(x)); - }; - }; - - var compose3 = function(f, g, x) { - return f(g(x)); - }; - - var filter = function(pred, m) { - return m.chain(function(x) { - return pred(x) ? m.of(x) : m.empty(); - }); - }; - - var inspect = /* istanbul ignore next */ function() { - return this.toString(); - }; - - // negativeZero :: a -> Boolean - var negativeZero = R.either(R.equals(-0), - R.equals(new Number(-0))); // jshint ignore:line - //. ### Classify //# type :: a -> String @@ -2900,6 +2947,14 @@ return S; + }; + + // Export two versions of the Sanctuary module: one with type checking; + // one without. + var S = createSanctuary(true); + S.unchecked = createSanctuary(false); + return S; + })); //. [Apply]: https://github.com/fantasyland/fantasy-land#apply diff --git a/package.json b/package.json index 827334a7..eb0e7807 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ }, "dependencies": { "ramda": "0.19.x", - "sanctuary-def": "0.3.x" + "sanctuary-def": "0.4.x" }, "devDependencies": { "doctest": "0.10.x", diff --git a/test/index.js b/test/index.js index 59319e42..04082a2e 100644 --- a/test/index.js +++ b/test/index.js @@ -4482,3 +4482,17 @@ describe('string', function() { }); }); + +describe('unchecked', function() { + + it('has the same properties as the top-level module', function() { + eq(R.sortBy(S.I, R.keys(S.unchecked)), + R.sortBy(S.I, R.without(['unchecked'], R.keys(S)))); + }); + + it('provides functions which do not perform type checking', function() { + eq(S.unchecked.inc(42), S.inc(42)); + eq(S.unchecked.inc('XXX'), 'XXX1'); + }); + +});