From e196bc10a40e27464340df80a4cf72e8d79d2335 Mon Sep 17 00:00:00 2001 From: David Chambers Date: Wed, 27 Jan 2016 20:14:22 -0800 Subject: [PATCH] make type checking optional --- README.md | 15 ++++++++++-- index.js | 64 +++++++++++++++++++++++++++++---------------------- test/index.js | 49 ++++++++++++++++++++++++++++++--------- 3 files changed, 87 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index f1c3cef..363a7a5 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,15 @@ const env = $.env.concat([Integer, NonZeroInteger]); The next step is to define a `def` function for the environment: ```javascript -const def = $.create(env); +const def = $.create(true, env); +``` + +The first argument to `$.create` determines whether type checking is enabled. +This allows one to only pay the performance cost of run-time type checking +during development. For example: + +```javascript +const def = $.create(process.env.NODE_ENV === 'development', env); ``` `def` is a function for defining functions. For example: @@ -650,7 +658,10 @@ For example: // TimeUnit :: Type const TimeUnit = $.EnumType(['milliseconds', 'seconds', 'minutes', 'hours']); -const def = $.create($.env.concat([TimeUnit, $.ValidDate, $.ValidNumber])); +// env :: [Type] +const env = $.env.concat([TimeUnit, $.ValidDate, $.ValidNumber]); + +const def = $.create(true, env); // convertTo :: TimeUnit -> ValidDate -> ValidNumber const convertTo = diff --git a/index.js b/index.js index 2113362..be52a90 100644 --- a/index.js +++ b/index.js @@ -671,8 +671,8 @@ ); }; - // create :: [Type] -> Function - $.create = function(_env) { + // create :: (Boolean, [Type]) -> Function + $.create = function(checkTypes, _env) { // env :: [Type] var env = map(_env, function(x) { return typeof x === 'function' ? @@ -795,11 +795,13 @@ var curry = function(name, constraints, expArgTypes, expRetType, _typeVarMap, _values, _indexes, impl) { return arity(_indexes.length, function() { - var delta = _indexes.length - arguments.length; - if (delta < 0) { - throw invalidArgumentsLength(name, - expArgTypes.length, - expArgTypes.length - delta); + if (checkTypes) { + var delta = _indexes.length - arguments.length; + if (delta < 0) { + throw invalidArgumentsLength(name, + expArgTypes.length, + expArgTypes.length - delta); + } } var $typeVarMap = {}; for (var typeVarName in _typeVarMap) { @@ -816,14 +818,16 @@ arguments[idx]['@@functional/placeholder'] === true)) { var value = arguments[idx]; - var expType = expArgTypes[index]; - if (!expType.test(value) || - isEmpty(satisfactoryTypes(name, constraints, $typeVarMap, - expType, value, index))) { - throw invalidValue(name, - replaceTypeVars($typeVarMap)(expType), - value, - index); + if (checkTypes) { + var expType = expArgTypes[index]; + if (!expType.test(value) || + isEmpty(satisfactoryTypes(name, constraints, $typeVarMap, + expType, value, index))) { + throw invalidValue(name, + replaceTypeVars($typeVarMap)(expType), + value, + index); + } } values[index] = value; } else { @@ -832,11 +836,13 @@ } if (isEmpty(indexes)) { var returnValue = impl.apply(this, values); - if (!expRetType.test(returnValue)) { - throw invalidReturnValue(name, [expRetType], returnValue); + if (checkTypes) { + if (!expRetType.test(returnValue)) { + throw invalidReturnValue(name, [expRetType], returnValue); + } + satisfactoryTypes(name, constraints, $typeVarMap, + expRetType, returnValue, NaN); } - satisfactoryTypes(name, constraints, $typeVarMap, - expRetType, returnValue, NaN); return returnValue; } else { return curry(name, constraints, expArgTypes, expRetType, @@ -846,15 +852,17 @@ }; return function def(name, constraints, expTypes, impl) { - if (arguments.length !== def.length) { - throw invalidArgumentsLength('def', def.length, arguments.length); - } + if (checkTypes) { + if (arguments.length !== def.length) { + throw invalidArgumentsLength('def', def.length, arguments.length); + } - var Type = RecordType({test: $.Function}); - var types = [$.String, $.Object, $.Array(Type), $.Function]; - for (var idx = 0; idx < types.length; idx += 1) { - if (!types[idx].test(arguments[idx])) { - throw invalidArgument('def', [types[idx]], arguments[idx], idx); + var Type = RecordType({test: $.Function}); + var types = [$.String, $.Object, $.Array(Type), $.Function]; + for (var idx = 0; idx < types.length; idx += 1) { + if (!types[idx].test(arguments[idx])) { + throw invalidArgument('def', [types[idx]], arguments[idx], idx); + } } } @@ -867,7 +875,7 @@ ); } - assertExpectedTypesInEnvironment(name)(expTypes); + if (checkTypes) assertExpectedTypesInEnvironment(name)(expTypes); return curry(name, constraints, diff --git a/test/index.js b/test/index.js index d2675cf..1ea6001 100644 --- a/test/index.js +++ b/test/index.js @@ -24,7 +24,7 @@ var errorEq = R.curry(function(type, message, error) { }); -var def = $.create($.env); +var def = $.create(true, $.env); var a = $.TypeVariable('a'); var b = $.TypeVariable('b'); @@ -159,7 +159,7 @@ var $Pair = $.BinaryType( describe('def', function() { - it('type checks its arguments', function() { + it('type checks its arguments when checkTypes is true', function() { throws(function() { def(); }, errorEq(TypeError, '‘def’ requires four arguments; received zero arguments')); @@ -186,6 +186,24 @@ describe('def', function() { 'as its fourth argument; received null')); }); + it('does not type check its arguments when checkTypes is false', function() { + var def = $.create(false, $.env); + + // add :: Number -> Number -> Number + var add = + def('add', + {}, + [$.Number, $.Number, $.Number], + function(x, y) { return x + y; }); + + eq(add(42, 1), 43); + eq(add(42)(1), 43); + eq(add(1, 2, 3, 4), 3); + eq(add(1)(2, 3, 4), 3); + eq(add('XXX', {foo: 42}), 'XXX[object Object]'); + eq(add({foo: 42}, 'XXX'), '[object Object]XXX'); + }); + it('returns a function whose length matches that of given list', function() { eq($0.length, 0); eq($1.length, 1); @@ -427,7 +445,8 @@ describe('def', function() { }); it('supports custom types', function() { - var def = $.create($.env.concat([Integer, $Pair])); + var env = $.env.concat([Integer, $Pair]); + var def = $.create(true, env); var T = $.Array($Pair($.String, Maybe($.Number))); @@ -468,7 +487,8 @@ describe('def', function() { // TimeUnit :: Type var TimeUnit = $.EnumType(['milliseconds', 'seconds', 'minutes', 'hours']); - var def = $.create($.env.concat([TimeUnit, $.ValidDate, $.ValidNumber])); + var env = $.env.concat([TimeUnit, $.ValidDate, $.ValidNumber]); + var def = $.create(true, env); // convertTo :: TimeUnit -> ValidDate -> ValidNumber var convertTo = @@ -495,7 +515,8 @@ describe('def', function() { // SillyType :: Type var SillyType = $.EnumType(['foo', true, 42]); - var _def = $.create($.env.concat([SillyType])); + var _env = $.env.concat([SillyType]); + var _def = $.create(true, _env); // id :: a -> a var id = _def('id', {}, [a, a], R.identity); @@ -529,7 +550,8 @@ describe('def', function() { // Line :: Type var Line = $.RecordType({start: Point, end: Point}); - var def = $.create($.env.concat([Point, Line])); + var env = $.env.concat([Point, Line]); + var def = $.create(true, env); // dist :: Point -> Point -> Number var dist = def('dist', {}, [Point, Point, $.Number], function(p, q) { @@ -587,7 +609,8 @@ describe('def', function() { }); it('supports "nullable" types', function() { - var def = $.create($.env.concat([$.Nullable])); + var env = $.env.concat([$.Nullable]); + var def = $.create(true, env); // toUpper :: Nullable String -> Nullable String var toUpper = @@ -632,7 +655,8 @@ describe('def', function() { }); it('supports the "ValidDate" type', function() { - var def = $.create($.env.concat([$.ValidDate])); + var env = $.env.concat([$.ValidDate]); + var def = $.create(true, env); // sinceEpoch :: ValidDate -> Number var sinceEpoch = def('sinceEpoch', @@ -857,7 +881,8 @@ describe('def', function() { }); it('supports polymorphism via type variables', function() { - var def = $.create($.env.concat([Either, Maybe, $Pair])); + var env = $.env.concat([Either, Maybe, $Pair]); + var def = $.create(true, env); // aa :: a -> a -> (a, a) var aa = def('aa', {}, [a, a, $Pair(a, a)], Pair); @@ -946,7 +971,8 @@ describe('def', function() { }); it('does not allow heterogeneous arrays', function() { - var def = $.create($.env.concat([Either])); + var env = $.env.concat([Either]); + var def = $.create(true, env); // concat :: [a] -> [a] -> [a] var concat = @@ -981,7 +1007,8 @@ describe('def', function() { }); it('supports type-class constraints', function() { - var def = $.create($.env.concat([Integer, Maybe, Either])); + var env = $.env.concat([Integer, Maybe, Either]); + var def = $.create(true, env); // hasMethods :: [String] -> a -> Boolean var hasMethods = R.curry(function(names, x) {