Skip to content

Commit

Permalink
Change type validators to use predicate instead of TypeRep
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Avaq committed Apr 23, 2016
1 parent e3c2db2 commit c30cbbd
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 127 deletions.
79 changes: 36 additions & 43 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@
//. > S.is(String, 42)
//. false
//. ```
var is = S.is =
S.is =
def('is',
{},
[TypeRep, $.Any, $.Boolean],
Expand Down Expand Up @@ -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
//.
Expand Down Expand Up @@ -2440,66 +2439,61 @@
//. ```
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) {
return Nothing();
}
x = x[keys[idx]];
}
return filter(is(type), Just(x));
return filter(pred, Just(x));
});

//. ### Number
Expand Down Expand Up @@ -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

Expand Down
52 changes: 28 additions & 24 deletions test/get.js
Original file line number Diff line number Diff line change
@@ -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() {
Expand All @@ -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));
});

});
60 changes: 33 additions & 27 deletions test/gets.js
Original file line number Diff line number Diff line change
@@ -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() {
Expand All @@ -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));
});

});
Loading

0 comments on commit c30cbbd

Please sign in to comment.