Skip to content

Commit

Permalink
add S.create for creating Sanctuary modules with custom environments
Browse files Browse the repository at this point in the history
  • Loading branch information
davidchambers committed May 1, 2016
1 parent b149c57 commit 1c5ce03
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 43 deletions.
63 changes: 40 additions & 23 deletions index.js
Expand Up @@ -126,21 +126,17 @@
//.
//. 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.
//. [`create`](#create) facilitates the creation of a Sanctuary module which
//. does not perform type checking.
//.
//. When application of `S.unchecked.<name>` honours the function's type
//. signature the result will be the same as if `S.<name>` 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:
//. In Node, one could use an environment variable to determine whether to
//. perform type checking:
//.
//. ```javascript
//. const S = process.env.NODE_ENV === 'production' ?
//. require('sanctuary').unchecked :
//. require('sanctuary');
//. const sanctuary = require('sanctuary');
//.
//. const checkTypes = process.env.NODE_ENV !== 'production';
//. const S = sanctuary.create(checkTypes, sanctuary.env);
//. ```
//.
//. ## API
Expand Down Expand Up @@ -272,7 +268,7 @@
// $Either :: Type -> Type -> Type
var $Either = $.BinaryType(
'sanctuary/Either',
function(x) { return x != null && x['@@type'] === 'sanctuary/Either'; },
function(x) { return _type(x) === 'sanctuary/Either'; },
function(either) { return either.isLeft ? [either.value] : []; },
function(either) { return either.isRight ? [either.value] : []; }
);
Expand All @@ -294,7 +290,7 @@
// $Maybe :: Type -> Type
var $Maybe = $.UnaryType(
'sanctuary/Maybe',
function(x) { return x != null && x['@@type'] === 'sanctuary/Maybe'; },
function(x) { return _type(x) === 'sanctuary/Maybe'; },
function(maybe) { return maybe.isJust ? [maybe.value] : []; }
);

Expand All @@ -309,8 +305,8 @@
}
);

// env :: Array Type
var env = $.env.concat([
// defaultEnv :: Array Type
var defaultEnv = $.env.concat([
$.FiniteNumber,
$.NonZeroFiniteNumber,
$Either,
Expand All @@ -322,13 +318,38 @@
$.ValidNumber
]);

// createSanctuary :: Boolean -> Module
var createSanctuary = function(checkTypes) {
// createSanctuary :: (Boolean, Array Type) -> Module
var createSanctuary = function createSanctuary(checkTypes, env) {

// To avoid excessive indentation this function's body is not indented.

var S = {EitherType: $Either, MaybeType: $Maybe};

//# create :: Boolean -> Array Type -> Module
//.
//. Takes a Boolean (specifying whether to enable type checking) and an
//. environment, and returns a Sanctuary module. The module's polymorphic
//. functions (such as [`I`](#I)) require each value associated with a type
//. variable to be a member of at least one type in the environment.
//.
//. A well-typed application of a Sanctuary function will produce the same
//. result regardless of whether type checking is enabled. If type checking
//. is enabled, a badly typed application will produce an exception with a
//. descriptive error message.
//.
//. See also [`env`](#env).
S.create =
$.create(checkTypes, [])('create',
{},
[$.Boolean, $.Array($.Any), $.Object],
createSanctuary);

//# env :: Array Type
//.
//. The default environment, which may be used as is or as the basis of a
//. custom environment in conjunction with [`create`](#create).
S.env = defaultEnv;

var def = $.create(checkTypes, env);

// Note: Type checking of method arguments takes place once all arguments
Expand Down Expand Up @@ -3203,11 +3224,7 @@

};

// Export two versions of the Sanctuary module: one with type checking;
// one without.
var S = createSanctuary(true);
S.unchecked = createSanctuary(false);
return S;
return createSanctuary(true, defaultEnv);

}));

Expand Down
95 changes: 95 additions & 0 deletions test/create.js
@@ -0,0 +1,95 @@
'use strict';

var throws = require('assert').throws;

var R = require('ramda');
var $ = require('sanctuary-def');

var eq = require('./utils').eq;
var errorEq = require('./utils').errorEq;
var S = require('..');


// customEnv :: Array Type
var customEnv = S.env.concat([$.EnumType(['foo', true, 42])]);

var checkedDefaultEnv = S.create(true, S.env);
var checkedCustomEnv = S.create(true, customEnv);
var uncheckedDefaultEnv = S.create(false, S.env);
var uncheckedCustomEnv = S.create(false, customEnv);


describe('create', function() {

it('is a binary function', function() {
eq(typeof S.create, 'function');
eq(S.create.length, 2);
});

it('type checks its arguments', function() {
throws(function() { S.create([1, 2, 3]); },
errorEq(TypeError,
'Invalid value\n' +
'\n' +
'create :: Boolean -> Array Any -> Object\n' +
' ^^^^^^^\n' +
' 1\n' +
'\n' +
'1) [1, 2, 3] :: Array\n' +
'\n' +
'The value at position 1 is not a member of ‘Boolean’.\n'));

throws(function() { S.create(true, false); },
errorEq(TypeError,
'Invalid value\n' +
'\n' +
'create :: Boolean -> Array Any -> Object\n' +
' ^^^^^^^^^\n' +
' 1\n' +
'\n' +
'1) false :: Boolean\n' +
'\n' +
'The value at position 1 is not a member of ‘Array Any’.\n'));
});

it('returns a Sanctuary module', function() {
eq(R.sortBy(S.I, R.keys(checkedDefaultEnv)),
R.sortBy(S.I, R.keys(S)));

eq(R.sortBy(S.I, R.keys(checkedCustomEnv)),
R.sortBy(S.I, R.keys(S)));

eq(R.sortBy(S.I, R.keys(uncheckedDefaultEnv)),
R.sortBy(S.I, R.keys(S)));

eq(R.sortBy(S.I, R.keys(uncheckedCustomEnv)),
R.sortBy(S.I, R.keys(S)));
});

it('can create a module which does not perform type checking', function() {
eq(uncheckedDefaultEnv.inc(42), S.inc(42));
eq(uncheckedDefaultEnv.inc('XXX'), 'XXX1');
});

it('can create a module with a custom environment', function() {
throws(function() { S.I(['foo', 'foo', 42]); },
errorEq(TypeError,
'Type-variable constraint violation\n' +
'\n' +
'I :: a -> a\n' +
' ^\n' +
' 1\n' +
'\n' +
'1) ["foo", "foo", 42] :: Array ???\n' +
'\n' +
'Since there is no type of which all the above values are members, the type-variable constraint has been violated.\n'));

eq(checkedCustomEnv.I(['foo', 'foo', 42]), ['foo', 'foo', 42]);
});

it('is curried', function() {
eq(S.create(true).length, 1);
eq(S.create(true)(S.env).I(42), 42);
});

});
20 changes: 0 additions & 20 deletions test/unchecked.js

This file was deleted.

0 comments on commit 1c5ce03

Please sign in to comment.