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 7, 2016
1 parent b149c57 commit d7ce861
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 44 deletions.
68 changes: 44 additions & 24 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,14 +318,42 @@
$.ValidNumber
]);

// createSanctuary :: Boolean -> Module
var createSanctuary = function(checkTypes) {
// Options :: Array Type
var Options = $.RecordType({checkTypes: $.Boolean, env: $.Array($.Any)});

// createSanctuary :: Options -> Module
var createSanctuary = function createSanctuary(opts) {

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

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

var def = $.create(checkTypes, env);
//# create :: { checkTypes :: Boolean, env :: Array Type } -> Module
//.
//. Takes an options record and returns a Sanctuary module. `checkTypes`
//. specifies whether to enable type checking. 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(opts.checkTypes, [])('create',
{},
[Options, $.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(opts.checkTypes, opts.env);

// Note: Type checking of method arguments takes place once all arguments
// have been provided (whereas function arguments are checked as early as
Expand Down Expand Up @@ -3203,11 +3227,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({checkTypes: true, env: defaultEnv});

}));

Expand Down
84 changes: 84 additions & 0 deletions test/create.js
@@ -0,0 +1,84 @@
'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({checkTypes: true, env: S.env});
var checkedCustomEnv = S.create({checkTypes: true, env: customEnv});
var uncheckedDefaultEnv = S.create({checkTypes: false, env: S.env});
var uncheckedCustomEnv = S.create({checkTypes: false, env: customEnv});


describe('create', function() {

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

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

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

it('returns a Sanctuary module', function() {
var expected = R.keys(S).sort();
eq(R.keys(checkedDefaultEnv).sort(), expected);
eq(R.keys(checkedCustomEnv).sort(), expected);
eq(R.keys(uncheckedDefaultEnv).sort(), expected);
eq(R.keys(uncheckedCustomEnv).sort(), expected);
});

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]);
});

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

This file was deleted.

0 comments on commit d7ce861

Please sign in to comment.