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()
* parseJson()

The only function untouched is `is()`, for it is used
to turn a TypeRep into a predicate.
  • Loading branch information
Avaq committed Dec 25, 2016
1 parent bd2fb5c commit 35621de
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 93 deletions.
78 changes: 36 additions & 42 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2787,64 +2787,60 @@
}
S.prop = def('prop', {a: [Accessible]}, [$.String, a, b], 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'})
//. Nothing
//. > S.get(S.is(Number), 'x', {x: '1', y: '2'})
//. Nothing()
//.
//. > S.get(Number, 'x', {})
//. Nothing
//. > S.get(S.is(Number), 'x', {})
//. Nothing()
//. ```
function get(typeRep, key, obj) {
function get(pred, key, obj) {
var x = obj[key];
return is(typeRep, x) ? Just(x) : Nothing;
return pred(x) ? Just(x) : Nothing;
}
S.get =
def('get', {a: [Accessible]}, [TypeRep, $.String, a, $Maybe(b)], get);
def('get', {a: [Accessible]}, [$.Function, $.String, a, $Maybe(c)], get);

//# gets :: Accessible a => TypeRep b -> Array String -> a -> Maybe b
//# gets :: Accessible a => (b -> Boolean) -> Array String -> a -> Maybe c
//.
//. Takes a [type representative](#type-representatives), an array of
//. property names, and an object and returns Just the value at the path
//. specified by the array of property names if such a path exists and
//. the value is of the specified type; Nothing otherwise.
//. Takes a predicate, an array of property names, and an object and returns
//. Just the value at the path specified by the array 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'}}})
//. Nothing
//. > S.gets(S.is(Number), ['a', 'b', 'c'], {a: {b: {c: '42'}}})
//. Nothing()
//.
//. > S.gets(Number, ['a', 'b', 'c'], {})
//. Nothing
//. > S.gets(S.is(Number), ['a', 'b', 'c'], {})
//. Nothing()
//. ```
function gets(typeRep, keys, obj) {
function gets(pred, keys, obj) {
var maybe = keys.reduce(function(m, k) {
return m.chain(function(x) { return x == null ? Nothing : Just(x[k]); });
}, Just(obj));
return filter(function(x) { return is(typeRep, x); }, maybe);
return filter(function(x) { return pred(x); }, maybe);
}
S.gets =
def('gets',
{a: [Accessible]},
[TypeRep, $.Array($.String), a, $Maybe(b)],
[$.Function, $.Array($.String), a, $Maybe(c)],
gets);

//# keys :: StrMap a -> Array String
Expand Down Expand Up @@ -3255,29 +3251,27 @@
S.parseInt =
def('parseInt', {}, [$.Integer, $.String, $Maybe($.Integer)], parseInt_);

//# 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, '[')
//. Nothing
//. > S.parseJson(S.is(Array), '[')
//. Nothing()
//.
//. > S.parseJson(Object, '["foo","bar","baz"]')
//. Nothing
//. > S.parseJson(S.is(Object), '["foo","bar","baz"]')
//. Nothing()
//. ```
function parseJson(typeRep, s) {
return filter(function(x) { return is(typeRep, x); },
encase(JSON.parse, s));
function parseJson(pred, s) {
return filter(function(x) { return pred(x); }, encase(JSON.parse, s));
}
S.parseJson =
def('parseJson', {}, [TypeRep, $.String, $Maybe(a)], parseJson);
def('parseJson', {}, [$.Function, $.String, $Maybe(b)], parseJson);

//. ### RegExp

Expand Down
37 changes: 18 additions & 19 deletions test/get.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
'use strict';

var vm = require('vm');

var S = require('..');

var vm = require('vm');
var eq = require('./internal/eq');
var throws = require('./internal/throws');

Expand All @@ -17,44 +16,44 @@ test('get', function() {
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(S.K(true), [1, 2, 3]); },
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(S.K(true), 'x', null); },
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');

eq(S.get(Number, 'x', {x: 0, y: 42}), S.Just(0));
eq(S.get(Number, 'y', {x: 0, y: 42}), S.Just(42));
eq(S.get(Number, 'z', {x: 0, y: 42}), S.Nothing);
eq(S.get(String, 'x', {x: 0, y: 42}), S.Nothing);
eq(S.get(S.K(true), 'x', {x: 0, y: 42}), S.Just(0));
eq(S.get(S.K(true), 'y', {x: 0, y: 42}), S.Just(42));
eq(S.get(S.K(true), 'z', {x: 0, y: 42}), S.Just(undefined));
eq(S.get(S.K(false), 'z', {x: 0, y: 42}), S.Nothing);
eq(S.get(S.K(false), 'x', {x: 0, y: 42}), S.Nothing);

eq(S.get(RegExp, 'x', {x: vm.runInNewContext('/.*/')}), S.Just(/.*/));
eq(S.get(vm.runInNewContext('RegExp'), 'x', {x: /.*/}), S.Just(/.*/));
eq(S.get(S.K(true), 'x', {x: vm.runInNewContext('/.*/')}), S.Just(/.*/));

});
42 changes: 20 additions & 22 deletions test/gets.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
'use strict';

var vm = require('vm');

var S = require('..');

var vm = require('vm');
var eq = require('./internal/eq');
var throws = require('./internal/throws');

Expand All @@ -17,47 +16,46 @@ test('gets', function() {
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(S.K(true), null); },
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(S.K(true), [], null); },
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');

eq(S.gets(Number, ['x'], {x: {z: 0}, y: 42}), S.Nothing);
eq(S.gets(Number, ['y'], {x: {z: 0}, y: 42}), S.Just(42));
eq(S.gets(Number, ['z'], {x: {z: 0}, y: 42}), S.Nothing);
eq(S.gets(Number, ['x', 'z'], {x: {z: 0}, y: 42}), S.Just(0));
eq(S.gets(Number, ['a', 'b', 'c'], {x: {z: 0}, y: 42}), S.Nothing);
eq(S.gets(Number, [], {x: {z: 0}, y: 42}), S.Nothing);
eq(S.gets(Object, [], {x: {z: 0}, y: 42}), S.Just({x: {z: 0}, y: 42}));
eq(S.gets(S.is(Number), ['x'], {x: {z: 0}, y: 42}), S.Nothing);
eq(S.gets(S.is(Number), ['y'], {x: {z: 0}, y: 42}), S.Just(42));
eq(S.gets(S.is(Number), ['z'], {x: {z: 0}, y: 42}), S.Nothing);
eq(S.gets(S.is(Number), ['x', 'z'], {x: {z: 0}, y: 42}), S.Just(0));
eq(S.gets(S.is(Number), ['a', 'b', 'c'], {x: {z: 0}, y: 42}), S.Nothing);
eq(S.gets(S.is(Number), [], {x: {z: 0}, y: 42}), S.Nothing);
eq(S.gets(S.is(Object), [], {x: {z: 0}, y: 42}), S.Just({x: {z: 0}, y: 42}));

eq(S.gets(RegExp, ['x'], {x: vm.runInNewContext('/.*/')}), S.Just(/.*/));
eq(S.gets(vm.runInNewContext('RegExp'), ['x'], {x: /.*/}), S.Just(/.*/));
eq(S.gets(S.K(true), ['x'], {x: vm.runInNewContext('/.*/')}), S.Just(/.*/));

});
20 changes: 10 additions & 10 deletions test/parseJson.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,28 @@ test('parseJson', function() {
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(S.K(true), [1, 2, 3]); },
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' +
'The value at position 1 is not a member of ‘String’.\n');

eq(S.parseJson(Object, '[Invalid JSON]'), S.Nothing);
eq(S.parseJson(Array, '{"foo":"bar"}'), S.Nothing);
eq(S.parseJson(Array, '["foo","bar"]'), S.Just(['foo', 'bar']));
eq(S.parseJson(S.K(true), '[Invalid JSON]'), S.Nothing);
eq(S.parseJson(S.K(false), '{"foo":"bar"}'), S.Nothing);
eq(S.parseJson(S.K(true), '["foo","bar"]'), S.Just(['foo', 'bar']));

});

0 comments on commit 35621de

Please sign in to comment.