From c30cbbd63835a49288db5ec7bdc68f81026aa2a3 Mon Sep 17 00:00:00 2001 From: Avaq Date: Sat, 23 Apr 2016 15:33:26 +0200 Subject: [PATCH] Change type validators to use predicate instead of TypeRep This commit will close #149 by changing all functions that have built-in type validation to use predicates instead of TypeReps to perform their validations. The functions affected are: * get() * gets() * pluck() * parseJson() The only function untouched is `is()`, for it is used to turn a TypeRep into a predicate. --- index.js | 79 +++++++++++++++++++++-------------------------- test/get.js | 52 +++++++++++++++++-------------- test/gets.js | 60 +++++++++++++++++++---------------- test/parseJson.js | 24 +++++++------- test/pluck.js | 39 ++++++++++------------- 5 files changed, 127 insertions(+), 127 deletions(-) diff --git a/index.js b/index.js index 3ff0c28c..909f2a16 100644 --- a/index.js +++ b/index.js @@ -411,7 +411,7 @@ //. > S.is(String, 42) //. false //. ``` - var is = S.is = + S.is = def('is', {}, [TypeRep, $.Any, $.Boolean], @@ -2341,25 +2341,24 @@ //. ``` S.lastIndexOf = sanctifyIndexOf('lastIndexOf'); - //# pluck :: Accessible a => TypeRep b -> String -> [a] -> [Maybe b] + //# pluck :: Accessible a => (b -> Boolean) -> String -> [a] -> [Maybe c] //. - //. Takes a [type representative](#type-representatives), a property name, - //. and a list of objects and returns a list of equal length. Each element - //. of the output list is Just the value of the specified property of the - //. corresponding object if the value is of the specified type (according - //. to [`is`](#is)); Nothing otherwise. + //. Takes a predicate, a property name, and a list of objects and returns a + //. list of equal length. Each element of the output list is Just the value of + //. the specified property of the corresponding object if the value passes the + //. given predicate; Nothing otherwise. //. //. See also [`get`](#get). //. //. ```javascript - //. > S.pluck(Number, 'x', [{x: 1}, {x: 2}, {x: '3'}, {x: null}, {}]) + //. > S.pluck(S.is(Number), 'x', [{x: 1}, {x: 2}, {x: '3'}, {x: null}, {}]) //. [Just(1), Just(2), Nothing(), Nothing(), Nothing()] //. ``` S.pluck = def('pluck', {a: [Accessible]}, - [TypeRep, $.String, $.Array(a), $.Array($Maybe(b))], - function(type, key, xs) { return R.map(get(type, key), xs); }); + [$.Function, $.String, $.Array(a), $.Array($Maybe(c))], + function(pred, key, xs) { return R.map(get(pred, key), xs); }); //# reduce :: Foldable f => (a -> b -> a) -> a -> f b -> a //. @@ -2440,58 +2439,53 @@ //. ``` S.prop = prop; - //# get :: Accessible a => TypeRep b -> String -> a -> Maybe b + //# get :: Accessible a => (b -> Boolean) -> String -> a -> Maybe c //. - //. Takes a [type representative](#type-representatives), a property - //. name, and an object and returns Just the value of the specified object - //. property if it is of the specified type (according to [`is`](#is)); + //. Takes a predicate, a property name, and an object and returns Just the + //. value of the specified object property if passes the given predicate; //. Nothing otherwise. //. - //. The `Object` type representative may be used as a catch-all since most - //. values have `Object.prototype` in their prototype chains. - //. //. See also [`gets`](#gets) and [`prop`](#prop). //. //. ```javascript - //. > S.get(Number, 'x', {x: 1, y: 2}) + //. > S.get(S.is(Number), 'x', {x: 1, y: 2}) //. Just(1) //. - //. > S.get(Number, 'x', {x: '1', y: '2'}) + //. > S.get(S.is(Number), 'x', {x: '1', y: '2'}) //. Nothing() //. - //. > S.get(Number, 'x', {}) + //. > S.get(S.is(Number), 'x', {}) //. Nothing() //. ``` var get = S.get = def('get', {a: [Accessible]}, - [TypeRep, $.String, a, $Maybe(b)], - function(type, key, obj) { return filter(is(type), Just(obj[key])); }); + [$.Function, $.String, a, $Maybe(c)], + function(pred, key, obj) { return filter(pred, Just(obj[key])); }); - //# gets :: Accessible a => TypeRep b -> [String] -> a -> Maybe b + //# gets :: Accessible a => (b -> Boolean) -> [String] -> a -> Maybe c //. - //. Takes a [type representative](#type-representatives), a list of property - //. names, and an object and returns Just the value at the path specified by - //. the list of property names if such a path exists and the value is of the - //. specified type; Nothing otherwise. + //. Takes a predicate, a list of property names, and an object and returns + //. Just the value at the path specified by the list of property names if such + //. a path exists and the value passes the given predicate; Nothing otherwise. //. //. See also [`get`](#get). //. //. ```javascript - //. > S.gets(Number, ['a', 'b', 'c'], {a: {b: {c: 42}}}) + //. > S.gets(S.is(Number), ['a', 'b', 'c'], {a: {b: {c: 42}}}) //. Just(42) //. - //. > S.gets(Number, ['a', 'b', 'c'], {a: {b: {c: '42'}}}) + //. > S.gets(S.is(Number), ['a', 'b', 'c'], {a: {b: {c: '42'}}}) //. Nothing() //. - //. > S.gets(Number, ['a', 'b', 'c'], {}) + //. > S.gets(S.is(Number), ['a', 'b', 'c'], {}) //. Nothing() //. ``` S.gets = def('gets', {a: [Accessible]}, - [TypeRep, $.Array($.String), a, $Maybe(b)], - function(type, keys, obj) { + [$.Function, $.Array($.String), a, $Maybe(c)], + function(pred, keys, obj) { var x = obj; for (var idx = 0; idx < keys.length; idx += 1) { if (x == null) { @@ -2499,7 +2493,7 @@ } x = x[keys[idx]]; } - return filter(is(type), Just(x)); + return filter(pred, Just(x)); }); //. ### Number @@ -2814,28 +2808,27 @@ )(s); }); - //# parseJson :: TypeRep a -> String -> Maybe a + //# parseJson :: (a -> Boolean) -> String -> Maybe b //. - //. Takes a [type representative](#type-representatives) and a string which - //. may or may not be valid JSON, and returns Just the result of applying - //. `JSON.parse` to the string *if* the result is of the specified type - //. (according to [`is`](#is)); Nothing otherwise. + //. Takes a predicate and a string which may or may not be valid JSON, and + //. returns Just the result of applying `JSON.parse` to the string *if* the + //. result passes the predicate; Nothing otherwise. //. //. ```javascript - //. > S.parseJson(Array, '["foo","bar","baz"]') + //. > S.parseJson(S.is(Array), '["foo","bar","baz"]') //. Just(['foo', 'bar', 'baz']) //. - //. > S.parseJson(Array, '[') + //. > S.parseJson(S.is(Array), '[') //. Nothing() //. - //. > S.parseJson(Object, '["foo","bar","baz"]') + //. > S.parseJson(S.is(Object), '["foo","bar","baz"]') //. Nothing() //. ``` S.parseJson = def('parseJson', {}, - [TypeRep, $.String, $Maybe(a)], - function(type, s) { return filter(is(type), encase(JSON.parse, s)); }); + [$.Function, $.String, $Maybe(b)], + function(pred, s) { return filter(pred, encase(JSON.parse, s)); }); //. ### RegExp diff --git a/test/get.js b/test/get.js index f3fc57eb..0a9fc2a5 100644 --- a/test/get.js +++ b/test/get.js @@ -1,10 +1,11 @@ 'use strict'; var throws = require('assert').throws; -var vm = require('vm'); var eq = require('./utils').eq; var errorEq = require('./utils').errorEq; var S = require('..'); +var T = function() { return true; }; +var F = function() { return false; }; describe('get', function() { @@ -19,56 +20,59 @@ describe('get', function() { errorEq(TypeError, 'Invalid value\n' + '\n' + - 'get :: Accessible a => TypeRep -> String -> a -> Maybe b\n' + - ' ^^^^^^^\n' + + 'get :: Accessible a => Function -> String -> a -> Maybe c\n' + + ' ^^^^^^^^\n' + ' 1\n' + '\n' + '1) [1, 2, 3] :: Array Number, Array FiniteNumber, Array NonZeroFiniteNumber, Array Integer, Array ValidNumber\n' + '\n' + - 'The value at position 1 is not a member of ‘TypeRep’.\n')); + 'The value at position 1 is not a member of ‘Function’.\n')); - throws(function() { S.get(Number, [1, 2, 3]); }, + throws(function() { S.get(T, [1, 2, 3]); }, errorEq(TypeError, 'Invalid value\n' + '\n' + - 'get :: Accessible a => TypeRep -> String -> a -> Maybe b\n' + - ' ^^^^^^\n' + - ' 1\n' + + 'get :: Accessible a => Function -> String -> a -> Maybe c\n' + + ' ^^^^^^\n' + + ' 1\n' + '\n' + '1) [1, 2, 3] :: Array Number, Array FiniteNumber, Array NonZeroFiniteNumber, Array Integer, Array ValidNumber\n' + '\n' + 'The value at position 1 is not a member of ‘String’.\n')); - throws(function() { S.get(Number, 'x', null); }, + throws(function() { S.get(T, 'x', null); }, errorEq(TypeError, 'Type-class constraint violation\n' + '\n' + - 'get :: Accessible a => TypeRep -> String -> a -> Maybe b\n' + - ' ^^^^^^^^^^^^ ^\n' + - ' 1\n' + + 'get :: Accessible a => Function -> String -> a -> Maybe c\n' + + ' ^^^^^^^^^^^^ ^\n' + + ' 1\n' + '\n' + '1) null :: Null\n' + '\n' + '‘get’ requires ‘a’ to satisfy the Accessible type-class constraint; the value at position 1 does not.\n')); }); - it('returns a Maybe', function() { - var obj = {x: 0, y: 42}; - eq(S.get(Number, 'x', obj), S.Just(0)); - eq(S.get(Number, 'y', obj), S.Just(42)); - eq(S.get(Number, 'z', obj), S.Nothing()); - eq(S.get(String, 'x', obj), S.Nothing()); + it('returns Nothing if the predicate returns false', function() { + eq(S.get(F, 'x', {x: 1}), S.Nothing()); + }); + + it('returns Just if the predicate returns true', function() { + eq(S.get(T, 'x', {x: 1}), S.Just(1)); }); - it('does not rely on constructor identity', function() { - eq(S.get(RegExp, 'x', {x: vm.runInNewContext('/.*/')}), S.Just(/.*/)); - eq(S.get(vm.runInNewContext('RegExp'), 'x', {x: /.*/}), S.Just(/.*/)); + it('returns a Maybe', function() { + var obj = {x: 0, y: 42}; + eq(S.get(S.is(Number), 'x', obj), S.Just(0)); + eq(S.get(S.is(Number), 'y', obj), S.Just(42)); + eq(S.get(S.is(Number), 'z', obj), S.Nothing()); + eq(S.get(S.is(String), 'x', obj), S.Nothing()); }); it('is curried', function() { - eq(S.get(Number).length, 2); - eq(S.get(Number)('x').length, 1); - eq(S.get(Number)('x')({x: 42}), S.Just(42)); + eq(S.get(T).length, 2); + eq(S.get(T)('x').length, 1); + eq(S.get(T)('x')({x: 42}), S.Just(42)); }); }); diff --git a/test/gets.js b/test/gets.js index 3be58328..a59eefc0 100644 --- a/test/gets.js +++ b/test/gets.js @@ -1,11 +1,12 @@ 'use strict'; var throws = require('assert').throws; -var vm = require('vm'); var eq = require('./utils').eq; var errorEq = require('./utils').errorEq; var S = require('..'); +var T = function() { return true; }; +var F = function() { return false; }; describe('gets', function() { @@ -20,59 +21,64 @@ describe('gets', function() { errorEq(TypeError, 'Invalid value\n' + '\n' + - 'gets :: Accessible a => TypeRep -> Array String -> a -> Maybe b\n' + - ' ^^^^^^^\n' + + 'gets :: Accessible a => Function -> Array String -> a -> Maybe c\n' + + ' ^^^^^^^^\n' + ' 1\n' + '\n' + '1) [1, 2, 3] :: Array Number, Array FiniteNumber, Array NonZeroFiniteNumber, Array Integer, Array ValidNumber\n' + '\n' + - 'The value at position 1 is not a member of ‘TypeRep’.\n')); + 'The value at position 1 is not a member of ‘Function’.\n')); - throws(function() { S.gets(Number, null); }, + throws(function() { S.gets(T, null); }, errorEq(TypeError, 'Invalid value\n' + '\n' + - 'gets :: Accessible a => TypeRep -> Array String -> a -> Maybe b\n' + - ' ^^^^^^^^^^^^\n' + - ' 1\n' + + 'gets :: Accessible a => Function -> Array String -> a -> Maybe c\n' + + ' ^^^^^^^^^^^^\n' + + ' 1\n' + '\n' + '1) null :: Null\n' + '\n' + 'The value at position 1 is not a member of ‘Array String’.\n')); - throws(function() { S.gets(Number, [], null); }, + throws(function() { S.gets(T, [], null); }, errorEq(TypeError, 'Type-class constraint violation\n' + '\n' + - 'gets :: Accessible a => TypeRep -> Array String -> a -> Maybe b\n' + - ' ^^^^^^^^^^^^ ^\n' + - ' 1\n' + + 'gets :: Accessible a => Function -> Array String -> a -> Maybe c\n' + + ' ^^^^^^^^^^^^ ^\n' + + ' 1\n' + '\n' + '1) null :: Null\n' + '\n' + '‘gets’ requires ‘a’ to satisfy the Accessible type-class constraint; the value at position 1 does not.\n')); }); - it('returns a Maybe', function() { - var obj = {x: {z: 0}, y: 42}; - eq(S.gets(Number, ['x'], obj), S.Nothing()); - eq(S.gets(Number, ['y'], obj), S.Just(42)); - eq(S.gets(Number, ['z'], obj), S.Nothing()); - eq(S.gets(Number, ['x', 'z'], obj), S.Just(0)); - eq(S.gets(Number, ['a', 'b', 'c'], obj), S.Nothing()); - eq(S.gets(Number, [], obj), S.Nothing()); - eq(S.gets(Object, [], obj), S.Just({x: {z: 0}, y: 42})); + it('returns Nothing if the predicate returns false', function() { + eq(S.gets(F, [], {}), S.Nothing()); + eq(S.gets(F, ['x'], {x: 1}), S.Nothing()); + }); + + it('returns Just if the predicate returns true', function() { + eq(S.gets(T, [], {}), S.Just({})); + eq(S.gets(T, ['x'], {x: 1}), S.Just(1)); }); - it('does not rely on constructor identity', function() { - eq(S.gets(RegExp, ['x'], {x: vm.runInNewContext('/.*/')}), S.Just(/.*/)); - eq(S.gets(vm.runInNewContext('RegExp'), ['x'], {x: /.*/}), S.Just(/.*/)); + it('returns a Maybe', function() { + var obj = {x: {z: 0}, y: 42}; + eq(S.gets(S.is(Number), ['x'], obj), S.Nothing()); + eq(S.gets(S.is(Number), ['y'], obj), S.Just(42)); + eq(S.gets(S.is(Number), ['z'], obj), S.Nothing()); + eq(S.gets(S.is(Number), ['x', 'z'], obj), S.Just(0)); + eq(S.gets(S.is(Number), ['a', 'b', 'c'], obj), S.Nothing()); + eq(S.gets(S.is(Number), [], obj), S.Nothing()); + eq(S.gets(S.is(Object), [], obj), S.Just({x: {z: 0}, y: 42})); }); it('is curried', function() { - eq(S.gets(Number).length, 2); - eq(S.gets(Number)(['x']).length, 1); - eq(S.gets(Number)(['x'])({x: 42}), S.Just(42)); + eq(S.gets(T).length, 2); + eq(S.gets(T)(['x']).length, 1); + eq(S.gets(T)(['x'])({x: 42}), S.Just(42)); }); }); diff --git a/test/parseJson.js b/test/parseJson.js index 1c25b34c..ccbb9ca1 100644 --- a/test/parseJson.js +++ b/test/parseJson.js @@ -5,6 +5,8 @@ var throws = require('assert').throws; var eq = require('./utils').eq; var errorEq = require('./utils').errorEq; var S = require('..'); +var T = function() { return true; }; +var F = function() { return false; }; describe('parseJson', function() { @@ -19,21 +21,21 @@ describe('parseJson', function() { errorEq(TypeError, 'Invalid value\n' + '\n' + - 'parseJson :: TypeRep -> String -> Maybe a\n' + - ' ^^^^^^^\n' + + 'parseJson :: Function -> String -> Maybe b\n' + + ' ^^^^^^^^\n' + ' 1\n' + '\n' + '1) "String" :: String\n' + '\n' + - 'The value at position 1 is not a member of ‘TypeRep’.\n')); + 'The value at position 1 is not a member of ‘Function’.\n')); - throws(function() { S.parseJson(Array, [1, 2, 3]); }, + throws(function() { S.parseJson(T, [1, 2, 3]); }, errorEq(TypeError, 'Invalid value\n' + '\n' + - 'parseJson :: TypeRep -> String -> Maybe a\n' + - ' ^^^^^^\n' + - ' 1\n' + + 'parseJson :: Function -> String -> Maybe b\n' + + ' ^^^^^^\n' + + ' 1\n' + '\n' + '1) [1, 2, 3] :: Array Number, Array FiniteNumber, Array NonZeroFiniteNumber, Array Integer, Array ValidNumber\n' + '\n' + @@ -41,15 +43,15 @@ describe('parseJson', function() { }); it('returns a Just when applied to a valid JSON string', function() { - eq(S.parseJson(Array, '["foo","bar"]'), S.Just(['foo', 'bar'])); + eq(S.parseJson(T, '["foo","bar"]'), S.Just(['foo', 'bar'])); }); it('returns a Nothing when applied to an invalid JSON string', function() { - eq(S.parseJson(Object, '[Invalid JSON]'), S.Nothing()); + eq(S.parseJson(T, '[Invalid JSON]'), S.Nothing()); }); - it('returns a Nothing when the parsed result is not a member of the given type', function() { - eq(S.parseJson(Array, '{"foo":"bar"}'), S.Nothing()); + it('returns a Nothing when the predicate returns false', function() { + eq(S.parseJson(F, '{"foo":"bar"}'), S.Nothing()); }); }); diff --git a/test/pluck.js b/test/pluck.js index 84e4eb8e..065e9fb7 100644 --- a/test/pluck.js +++ b/test/pluck.js @@ -1,11 +1,11 @@ 'use strict'; var throws = require('assert').throws; -var vm = require('vm'); var eq = require('./utils').eq; var errorEq = require('./utils').errorEq; var S = require('..'); +var T = function() { return true; }; describe('pluck', function() { @@ -20,33 +20,33 @@ describe('pluck', function() { errorEq(TypeError, 'Invalid value\n' + '\n' + - 'pluck :: Accessible a => TypeRep -> String -> Array a -> Array (Maybe b)\n' + - ' ^^^^^^^\n' + + 'pluck :: Accessible a => Function -> String -> Array a -> Array (Maybe c)\n' + + ' ^^^^^^^^\n' + ' 1\n' + '\n' + '1) [1, 2, 3] :: Array Number, Array FiniteNumber, Array NonZeroFiniteNumber, Array Integer, Array ValidNumber\n' + '\n' + - 'The value at position 1 is not a member of ‘TypeRep’.\n')); + 'The value at position 1 is not a member of ‘Function’.\n')); - throws(function() { S.pluck(Number, [1, 2, 3]); }, + throws(function() { S.pluck(T, [1, 2, 3]); }, errorEq(TypeError, 'Invalid value\n' + '\n' + - 'pluck :: Accessible a => TypeRep -> String -> Array a -> Array (Maybe b)\n' + - ' ^^^^^^\n' + - ' 1\n' + + 'pluck :: Accessible a => Function -> String -> Array a -> Array (Maybe c)\n' + + ' ^^^^^^\n' + + ' 1\n' + '\n' + '1) [1, 2, 3] :: Array Number, Array FiniteNumber, Array NonZeroFiniteNumber, Array Integer, Array ValidNumber\n' + '\n' + 'The value at position 1 is not a member of ‘String’.\n')); - throws(function() { S.pluck(Number, 'x', {length: 0}); }, + throws(function() { S.pluck(T, 'x', {length: 0}); }, errorEq(TypeError, 'Invalid value\n' + '\n' + - 'pluck :: Accessible a => TypeRep -> String -> Array a -> Array (Maybe b)\n' + - ' ^^^^^^^\n' + - ' 1\n' + + 'pluck :: Accessible a => Function -> String -> Array a -> Array (Maybe c)\n' + + ' ^^^^^^^\n' + + ' 1\n' + '\n' + '1) {"length": 0} :: Object, StrMap Number, StrMap FiniteNumber, StrMap Integer, StrMap ValidNumber\n' + '\n' + @@ -55,20 +55,15 @@ describe('pluck', function() { it('returns a list of satisfactory plucked values', function() { var xs = [{x: '1'}, {x: 2}, {x: null}, {x: undefined}, {}]; - eq(S.pluck(Number, 'x', []), []); - eq(S.pluck(Number, 'x', xs), + eq(S.pluck(S.is(Number), 'x', []), []); + eq(S.pluck(S.is(Number), 'x', xs), [S.Nothing(), S.Just(2), S.Nothing(), S.Nothing(), S.Nothing()]); }); - it('does not rely on constructor identity', function() { - eq(S.pluck(Array, 'x', [{x: vm.runInNewContext('[0]')}]), [S.Just([0])]); - eq(S.pluck(vm.runInNewContext('Array'), 'x', [{x: [0]}]), [S.Just([0])]); - }); - it('is curried', function() { - eq(S.pluck(Number).length, 2); - eq(S.pluck(Number)('x').length, 1); - eq(S.pluck(Number)('x')([{x: 42}]), [S.Just(42)]); + eq(S.pluck(T).length, 2); + eq(S.pluck(T)('x').length, 1); + eq(S.pluck(T)('x')([{x: 42}]), [S.Just(42)]); }); });