Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add S.create for creating Sanctuary modules with custom environments #206

Merged
merged 1 commit into from
May 27, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.