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 27, 2016
1 parent 8ef03e0 commit ce5499f
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 42 deletions.
101 changes: 79 additions & 22 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,21 +128,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: checkTypes, env: sanctuary.env});
//. ```
//.
//. ## API
Expand Down Expand Up @@ -319,8 +315,8 @@
}
);

// env :: Array Type
var env = $.env.concat([
// defaultEnv :: Array Type
var defaultEnv = $.env.concat([
$.FiniteNumber,
$.NonZeroFiniteNumber,
$Either,
Expand All @@ -333,14 +329,79 @@
$.ValidNumber
]);

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

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

/* eslint-disable indent */

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

var def = $.create({checkTypes: checkTypes, env: 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.
//.
//. The following snippet demonstrates defining a custom type and using
//. `create` to produce a Sanctuary module which is aware of that type:
//.
//. ```javascript
//. const {create, env} = require('sanctuary');
//. const $ = require('sanctuary-def');
//.
//. // identityTypeName :: String
//. const identityTypeName = 'my-package/Identity';
//.
//. // Identity :: a -> Identity a
//. const Identity = function Identity(x) {
//. return {
//. '@@type': identityTypeName,
//. map: f => Identity(f(x)),
//. chain: f => f(x),
//. // ...
//. value: x,
//. };
//. };
//.
//. // isIdentity :: a -> Boolean
//. const isIdentity = x => x != null && x['@@type'] === identityTypeName;
//.
//. // identityToArray :: Identity a -> Array a
//. const identityToArray = identity => [identity.value];
//.
//. // IdentityType :: Type
//. const IdentityType =
//. $.UnaryType(identityTypeName, isIdentity, identityToArray);
//.
//. const S = create({
//. checkTypes: process.env.NODE_ENV !== 'production',
//. env: env.concat([IdentityType]),
//. });
//. ```
//.
//. See also [`env`](#env).
S.create =
$.create({checkTypes: opts.checkTypes, env: defaultEnv})('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);

// 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 @@ -3446,11 +3507,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
83 changes: 83 additions & 0 deletions test/create.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
'use strict';

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

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, StrMap ???\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) "true" :: String\n' +
'\n' +
'The value at position 1 is not a member of ‘Boolean’.\n'));
});

it('returns a Sanctuary module', function() {
var expected = Object.keys(S).sort();
eq(Object.keys(checkedDefaultEnv).sort(), expected);
eq(Object.keys(checkedCustomEnv).sort(), expected);
eq(Object.keys(uncheckedDefaultEnv).sort(), expected);
eq(Object.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 ce5499f

Please sign in to comment.