From 50e91647503314183dda42cd84ae48387b5123b9 Mon Sep 17 00:00:00 2001 From: Aldwin Vlasblom Date: Mon, 26 Jun 2017 16:13:22 +0200 Subject: [PATCH 01/20] Add parent type argument to NullaryType --- index.js | 261 ++++++++++++++++++++++++++++++-------------------- test/index.js | 8 +- 2 files changed, 160 insertions(+), 109 deletions(-) diff --git a/index.js b/index.js index fce9e36..afdf9c3 100644 --- a/index.js +++ b/index.js @@ -353,6 +353,7 @@ name, // :: String url, // :: String format, // :: (String -> String, String -> String -> String) -> String + parent, // :: Type test, // :: Any -> Boolean keys, // :: Array String types // :: StrMap { extractor :: a -> Array b, type :: Type } @@ -362,27 +363,35 @@ this.keys = keys; this.name = name; this.type = type; + this.parent = parent; this.types = types; this.url = url; } _Type['@@type'] = 'sanctuary-def/Type'; + _Type.prototype.test = function(x) { + return this.parent.test(x) && this._test(x); + }; + _Type.prototype.validate = function(x) { - if (!this._test(x)) return Left({value: x, propPath: []}); - for (var idx = 0; idx < this.keys.length; idx += 1) { - var k = this.keys[idx]; - var t = this.types[k]; - for (var idx2 = 0, ys = t.extractor(x); idx2 < ys.length; idx2 += 1) { - var result = t.type.validate(ys[idx2]); - if (result.isLeft) { - var value = result.value.value; - var propPath = Z.concat([k], result.value.propPath); - return Left({value: value, propPath: propPath}); + var self = this; + return Z.chain(function(x) { + if (!self._test(x)) return Left({value: x, propPath: []}); + for (var idx = 0; idx < self.keys.length; idx += 1) { + var k = self.keys[idx]; + var t = self.types[k]; + for (var idx2 = 0, ys = t.extractor(x); idx2 < ys.length; idx2 += 1) { + var result = t.type.validate(ys[idx2]); + if (result.isLeft) { + var value = result.value.value; + var propPath = Z.concat([k], result.value.propPath); + return Left({value: value, propPath: propPath}); + } } } - } - return Right(x); + return Right(x); + }, self.parent.validate(x)); }; _Type.prototype.toString = function() { @@ -398,10 +407,6 @@ var UNKNOWN = 'UNKNOWN'; var VARIABLE = 'VARIABLE'; - // Inconsistent :: Type - var Inconsistent = - new _Type(INCONSISTENT, '', '', always2('???'), K(false), [], {}); - // typeEq :: String -> a -> Boolean function typeEq(name) { return function(x) { @@ -424,9 +429,9 @@ '#' + stripNamespace(name); } - // NullaryTypeWithUrl :: (String, Any -> Boolean) -> Type - function NullaryTypeWithUrl(name, test) { - return NullaryType(name, functionUrl(name), test); + // NullaryTypeWithUrl :: (String, Type, Any -> Boolean) -> Type + function NullaryTypeWithUrl(name, parent, test) { + return NullaryType(name, functionUrl(name), parent, test); } // EnumTypeWithUrl :: (String, Array Any) -> Type @@ -456,17 +461,29 @@ //# Any :: Type //. //. Type comprising every JavaScript value. - var Any = NullaryTypeWithUrl('sanctuary-def/Any', K(true)); + var Any = NullaryTypeWithUrl('sanctuary-def/Any', null, null); + Any.parent = Any; + Any.test = K(true); + Any.validate = Right; + + //# None :: Type + //. + //. Type with no members. + var None = NullaryTypeWithUrl('sanctuary-def/None', Any, K(false)); + + // Inconsistent :: Type + var Inconsistent = + new _Type(INCONSISTENT, '', '', always2('???'), None, K(false), [], {}); //# AnyFunction :: Type //. //. Type comprising every Function value. - var AnyFunction = NullaryTypeWithUrl('Function', typeofEq('function')); + var AnyFunction = NullaryTypeWithUrl('Function', Any, typeofEq('function')); //# Arguments :: Type //. //. Type comprising every [`arguments`][arguments] object. - var Arguments = NullaryTypeWithUrl('Arguments', typeEq('Arguments')); + var Arguments = NullaryTypeWithUrl('Arguments', Any, typeEq('Arguments')); //# Array :: Type -> Type //. @@ -476,18 +493,32 @@ //# Boolean :: Type //. //. Type comprising `true` and `false`. - var Boolean_ = NullaryTypeWithUrl('Boolean', typeofEq('boolean')); + var Boolean_ = NullaryTypeWithUrl('Boolean', Any, typeofEq('boolean')); //# Date :: Type //. //. Type comprising every Date value. - var Date_ = NullaryTypeWithUrl('Date', typeEq('Date')); + var Date_ = NullaryTypeWithUrl('Date', Any, typeEq('Date')); //# Error :: Type //. //. Type comprising every Error value, including values of more specific //. constructors such as [`SyntaxError`][] and [`TypeError`][]. - var Error_ = NullaryTypeWithUrl('Error', typeEq('Error')); + var Error_ = NullaryTypeWithUrl('Error', Any, typeEq('Error')); + + //# Number :: Type + //. + //. Type comprising every primitive Number value (including `NaN`). + var Number_ = NullaryTypeWithUrl('Number', Any, typeofEq('number')); + + //# ValidNumber :: Type + //. + //. Type comprising every [`Number`][] value except `NaN`. + var ValidNumber = NullaryTypeWithUrl( + 'sanctuary-def/ValidNumber', + Number_, + function(x) { return !isNaN(x); } + ); //# FiniteNumber :: Type //. @@ -495,7 +526,8 @@ //. `-Infinity`. var FiniteNumber = NullaryTypeWithUrl( 'sanctuary-def/FiniteNumber', - function(x) { return ValidNumber._test(x) && isFinite(x); } + ValidNumber, + isFinite ); //# Function :: Array Type -> Type @@ -522,8 +554,6 @@ last(xs)); } - var test = AnyFunction._test; - var $keys = []; var $types = {}; types.forEach(function(t, idx) { @@ -532,9 +562,21 @@ $types[k] = {extractor: K([]), type: t}; }); - return new _Type(FUNCTION, '', '', format, test, $keys, $types); + return new _Type(FUNCTION, + '', + '', + format, + AnyFunction, + K(true), + $keys, + $types); } + //# RegExp :: Type + //. + //. Type comprising every RegExp value. + var RegExp_ = NullaryTypeWithUrl('RegExp', Any, typeEq('RegExp')); + //# GlobalRegExp :: Type //. //. Type comprising every [`RegExp`][] value whose `global` flag is `true`. @@ -542,7 +584,8 @@ //. See also [`NonGlobalRegExp`][]. var GlobalRegExp = NullaryTypeWithUrl( 'sanctuary-def/GlobalRegExp', - function(x) { return RegExp_._test(x) && x.global; } + RegExp_, + function(x) { return x.global; } ); //# Integer :: Type @@ -551,9 +594,9 @@ //. [[`Number.MIN_SAFE_INTEGER`][min] .. [`Number.MAX_SAFE_INTEGER`][max]]. var Integer = NullaryTypeWithUrl( 'sanctuary-def/Integer', + ValidNumber, function(x) { - return ValidNumber._test(x) && - Math.floor(x) === x && + return Math.floor(x) === x && x >= MIN_SAFE_INTEGER && x <= MAX_SAFE_INTEGER; } @@ -564,7 +607,8 @@ //. Type comprising every [`FiniteNumber`][] value less than zero. var NegativeFiniteNumber = NullaryTypeWithUrl( 'sanctuary-def/NegativeFiniteNumber', - function(x) { return FiniteNumber._test(x) && x < 0; } + FiniteNumber, + function(x) { return x < 0; } ); //# NegativeInteger :: Type @@ -572,7 +616,8 @@ //. Type comprising every [`Integer`][] value less than zero. var NegativeInteger = NullaryTypeWithUrl( 'sanctuary-def/NegativeInteger', - function(x) { return Integer._test(x) && x < 0; } + Integer, + function(x) { return x < 0; } ); //# NegativeNumber :: Type @@ -580,7 +625,8 @@ //. Type comprising every [`Number`][] value less than zero. var NegativeNumber = NullaryTypeWithUrl( 'sanctuary-def/NegativeNumber', - function(x) { return Number_._test(x) && x < 0; } + Number_, + function(x) { return x < 0; } ); //# NonEmpty :: Type -> Type @@ -607,7 +653,8 @@ //. See also [`GlobalRegExp`][]. var NonGlobalRegExp = NullaryTypeWithUrl( 'sanctuary-def/NonGlobalRegExp', - function(x) { return RegExp_._test(x) && !x.global; } + RegExp_, + function(x) { return !x.global; } ); //# NonZeroFiniteNumber :: Type @@ -615,7 +662,8 @@ //. Type comprising every [`FiniteNumber`][] value except `0` and `-0`. var NonZeroFiniteNumber = NullaryTypeWithUrl( 'sanctuary-def/NonZeroFiniteNumber', - function(x) { return FiniteNumber._test(x) && x !== 0; } + FiniteNumber, + function(x) { return x !== 0; } ); //# NonZeroInteger :: Type @@ -623,7 +671,8 @@ //. Type comprising every [`Integer`][] value except `0` and `-0`. var NonZeroInteger = NullaryTypeWithUrl( 'sanctuary-def/NonZeroInteger', - function(x) { return Integer._test(x) && x !== 0; } + Integer, + function(x) { return x !== 0; } ); //# NonZeroValidNumber :: Type @@ -631,13 +680,14 @@ //. Type comprising every [`ValidNumber`][] value except `0` and `-0`. var NonZeroValidNumber = NullaryTypeWithUrl( 'sanctuary-def/NonZeroValidNumber', - function(x) { return ValidNumber._test(x) && x !== 0; } + ValidNumber, + function(x) { return x !== 0; } ); //# Null :: Type //. //. Type whose sole member is `null`. - var Null = NullaryTypeWithUrl('Null', typeEq('Null')); + var Null = NullaryTypeWithUrl('Null', Any, typeEq('Null')); //# Nullable :: Type -> Type //. @@ -651,11 +701,6 @@ } ); - //# Number :: Type - //. - //. Type comprising every primitive Number value (including `NaN`). - var Number_ = NullaryTypeWithUrl('Number', typeofEq('number')); - //# Object :: Type //. //. Type comprising every "plain" Object value. Specifically, values @@ -665,7 +710,7 @@ //. - [`Object.create`][]; or //. - the `new` operator in conjunction with `Object` or a custom //. constructor function. - var Object_ = NullaryTypeWithUrl('Object', typeEq('Object')); + var Object_ = NullaryTypeWithUrl('Object', Any, typeEq('Object')); //# Pair :: Type -> Type -> Type //. @@ -683,7 +728,8 @@ //. Type comprising every [`FiniteNumber`][] value greater than zero. var PositiveFiniteNumber = NullaryTypeWithUrl( 'sanctuary-def/PositiveFiniteNumber', - function(x) { return FiniteNumber._test(x) && x > 0; } + FiniteNumber, + function(x) { return x > 0; } ); //# PositiveInteger :: Type @@ -691,7 +737,8 @@ //. Type comprising every [`Integer`][] value greater than zero. var PositiveInteger = NullaryTypeWithUrl( 'sanctuary-def/PositiveInteger', - function(x) { return Integer._test(x) && x > 0; } + Integer, + function(x) { return x > 0; } ); //# PositiveNumber :: Type @@ -699,14 +746,10 @@ //. Type comprising every [`Number`][] value greater than zero. var PositiveNumber = NullaryTypeWithUrl( 'sanctuary-def/PositiveNumber', - function(x) { return Number_._test(x) && x > 0; } + Number_, + function(x) { return x > 0; } ); - //# RegExp :: Type - //. - //. Type comprising every RegExp value. - var RegExp_ = NullaryTypeWithUrl('RegExp', typeEq('RegExp')); - //# RegexFlags :: Type //. //. Type comprising the canonical RegExp flags: @@ -732,7 +775,7 @@ //. `{foo: 1, bar: 2, baz: 'XXX'}` is not. var StrMap = UnaryTypeWithUrl( 'sanctuary-def/StrMap', - Object_._test, + Object_.test.bind(Object_), function(strMap) { return Z.reduce(function(xs, x) { return xs.concat([x]); }, [], strMap); } @@ -741,28 +784,31 @@ //# String :: Type //. //. Type comprising every primitive String value. - var String_ = NullaryTypeWithUrl('String', typeofEq('string')); + var String_ = NullaryTypeWithUrl('String', Any, typeofEq('string')); //# Symbol :: Type //. //. Type comprising every Symbol value. - var Symbol_ = NullaryTypeWithUrl('Symbol', typeofEq('symbol')); + var Symbol_ = NullaryTypeWithUrl('Symbol', Any, typeofEq('symbol')); //# Type :: Type //. //. Type comprising every `Type` value. - var Type = NullaryTypeWithUrl('Type', typeEq('sanctuary-def/Type')); + var Type = NullaryTypeWithUrl('Type', Any, typeEq('sanctuary-def/Type')); //# TypeClass :: Type //. //. Type comprising every [`TypeClass`][] value. - var TypeClass = - NullaryTypeWithUrl('TypeClass', typeEq('sanctuary-type-classes/TypeClass')); + var TypeClass = NullaryTypeWithUrl( + 'TypeClass', + Any, + typeEq('sanctuary-type-classes/TypeClass') + ); //# Undefined :: Type //. //. Type whose sole member is `undefined`. - var Undefined = NullaryTypeWithUrl('Undefined', typeEq('Undefined')); + var Undefined = NullaryTypeWithUrl('Undefined', Any, typeEq('Undefined')); //# Unknown :: Type //. @@ -780,22 +826,16 @@ //. - `List (List (List Number))` //. - `List (List (List String))` //. - `...` - var Unknown = new _Type(UNKNOWN, '', '', always2('???'), K(true), [], {}); + var Unknown = + new _Type(UNKNOWN, '', '', always2('???'), Any, K(true), [], {}); //# ValidDate :: Type //. //. Type comprising every [`Date`][] value except `new Date(NaN)`. var ValidDate = NullaryTypeWithUrl( 'sanctuary-def/ValidDate', - function(x) { return Date_._test(x) && !isNaN(x.valueOf()); } - ); - - //# ValidNumber :: Type - //. - //. Type comprising every [`Number`][] value except `NaN`. - var ValidNumber = NullaryTypeWithUrl( - 'sanctuary-def/ValidNumber', - function(x) { return Number_._test(x) && !isNaN(x); } + Date_, + function(x) { return !isNaN(x.valueOf()); } ); //# env :: Array Type @@ -834,7 +874,7 @@ ]; // Unchecked :: String -> Type - function Unchecked(s) { return NullaryType(s, '', K(true)); } + function Unchecked(s) { return NullaryType(s, '', Any, K(true)); } var def = _create({checkTypes: true, env: env}); @@ -1267,20 +1307,9 @@ //. The environment is only significant if the type contains //. [type variables][]. //. - //. One may define a more restrictive type in terms of a more general one: - //. - //. ```javascript - //. // NonNegativeInteger :: Type - //. const NonNegativeInteger = $.NullaryType( - //. 'my-package/NonNegativeInteger', - //. 'http://example.com/my-package#NonNegativeInteger', - //. x => $.test([], $.Integer, x) && x >= 0 - //. ); - //. ``` - //. - //. Using types as predicates is useful in other contexts too. One could, - //. for example, define a [record type][] for each endpoint of a REST API - //. and validate the bodies of incoming POST requests against these types. + //. Using types as predicates is powerful. One could, for example, + //. define a [record type][] for each endpoint of a REST API and + //. validate the bodies of incoming POST requests against these types. function test(env, t, x) { var typeInfo = {name: 'name', constraints: {}, types: [t]}; return satisfactoryTypes(env, typeInfo, {}, t, 0, [], [x]).isRight; @@ -1290,7 +1319,7 @@ //. //. sanctuary-def provides several functions for defining types. - //# NullaryType :: String -> String -> (Any -> Boolean) -> Type + //# NullaryType :: String -> String -> Type -> (Any -> Boolean) -> Type //. //. Type constructor for types with no type variables (such as [`Number`][]). //. @@ -1300,6 +1329,8 @@ //. //. - the documentation URL of `t` (exposed as `t.url`); and //. + //. - the parent of `t` (exposed as `t.parent`); + //. //. - a predicate which accepts any JavaScript value and returns `true` if //. (and only if) the value is a member of `t`. //. @@ -1310,8 +1341,8 @@ //. const Integer = $.NullaryType( //. 'my-package/Integer', //. 'http://example.com/my-package#Integer', - //. x => typeof x === 'number' && - //. Math.floor(x) === x && + //. $.ValidNumber, + //. x => Math.floor(x) === x && //. x >= Number.MIN_SAFE_INTEGER && //. x <= Number.MAX_SAFE_INTEGER //. ); @@ -1320,7 +1351,8 @@ //. const NonZeroInteger = $.NullaryType( //. 'my-package/NonZeroInteger', //. 'http://example.com/my-package#NonZeroInteger', - //. x => $.test([], Integer, x) && x !== 0 + //. Integer, + //. x => x !== 0 //. ); //. //. // rem :: Integer -> NonZeroInteger -> Integer @@ -1352,17 +1384,17 @@ //. // //. // The value at position 1 is not a member of ‘NonZeroInteger’. //. ``` - function NullaryType(name, url, test) { + function NullaryType(name, url, parent, test) { function format(outer, inner) { return outer(stripNamespace(name)); } - return new _Type(NULLARY, name, url, format, test, [], {}); + return new _Type(NULLARY, name, url, format, parent, test, [], {}); } var CheckedNullaryType = def('NullaryType', {}, - [String_, String_, Function_([Any, Boolean_]), Type], + [String_, String_, Type, Function_([Any, Boolean_]), Type], NullaryType); //# UnaryType :: String -> String -> (Any -> Boolean) -> (t a -> Array a) -> (Type -> Type) @@ -1450,7 +1482,7 @@ inner('$1')(String($1)) + outer(')'); } var types = {$1: {extractor: _1, type: $1}}; - return new _Type(UNARY, name, url, format, test, ['$1'], types); + return new _Type(UNARY, name, url, format, Any, test, ['$1'], types); }; } @@ -1471,7 +1503,7 @@ // fromUnaryType :: Type -> (Type -> Type) function fromUnaryType(t) { - return UnaryType(t.name, t.url, t._test, t.types.$1.extractor); + return UnaryType(t.name, t.url, t.test.bind(t), t.types.$1.extractor); } //# BinaryType :: String -> String -> (Any -> Boolean) -> (t a b -> Array a) -> (t a b -> Array b) -> (Type -> Type -> Type) @@ -1533,14 +1565,16 @@ //. const Rank = $.NullaryType( //. 'my-package/Rank', //. 'http://example.com/my-package#Rank', - //. x => typeof x === 'string' && /^([A23456789JQK]|10)$/.test(x) + //. $.String, + //. x => /^([A23456789JQK]|10)$/.test(x) //. ); //. //. // Suit :: Type //. const Suit = $.NullaryType( //. 'my-package/Suit', //. 'http://example.com/my-package#Suit', - //. x => typeof x === 'string' && /^[\u2660\u2663\u2665\u2666]$/.test(x) + //. $.String, + //. x => /^[\u2660\u2663\u2665\u2666]$/.test(x) //. ); //. //. // Card :: Type @@ -1573,7 +1607,14 @@ } var types = {$1: {extractor: _1, type: $1}, $2: {extractor: _2, type: $2}}; - return new _Type(BINARY, name, url, format, test, ['$1', '$2'], types); + return new _Type(BINARY, + name, + url, + format, + Any, + test, + ['$1', '$2'], + types); }; } @@ -1597,7 +1638,7 @@ function xprod(t, $1s, $2s) { var specialize = BinaryType(t.name, t.url, - t._test, + t.test.bind(t), t.types.$1.extractor, t.types.$2.extractor); var $types = []; @@ -1632,7 +1673,7 @@ //. ); //. ``` function EnumType(name, url, members) { - return NullaryType(name, url, memberOf(members)); + return NullaryType(name, url, Any, memberOf(members)); } var CheckedEnumType = @@ -1710,7 +1751,7 @@ $types[k] = {extractor: function(x) { return [x[k]]; }, type: fields[k]}; }); - return new _Type(RECORD, '', '', format, test, keys, $types); + return new _Type(RECORD, '', '', format, Object_, test, keys, $types); } var CheckedRecordType = @@ -1770,7 +1811,7 @@ //. // Since there is no type of which all the above values are members, the type-variable constraint has been violated. //. ``` function TypeVariable(name) { - return new _Type(VARIABLE, name, '', always2(name), K(true), [], {}); + return new _Type(VARIABLE, name, '', always2(name), Any, K(true), [], {}); } var CheckedTypeVariable = @@ -1828,7 +1869,14 @@ return outer('(' + name + ' ') + inner('$1')(String($1)) + outer(')'); } var types = {$1: {extractor: K([]), type: $1}}; - return new _Type(VARIABLE, name, '', format, K(true), ['$1'], types); + return new _Type(VARIABLE, + name, + '', + format, + Any, + K(true), + ['$1'], + types); }; } @@ -1863,7 +1911,7 @@ var keys = ['$1', '$2']; var types = {$1: {extractor: K([]), type: $1}, $2: {extractor: K([]), type: $2}}; - return new _Type(VARIABLE, name, '', format, K(true), keys, types); + return new _Type(VARIABLE, name, '', format, Any, K(true), keys, types); }; } @@ -2495,7 +2543,7 @@ function fromUncheckedUnaryType(typeConstructor) { var t = typeConstructor(Unknown); var _1 = t.types.$1.extractor; - return CheckedUnaryType(t.name, t.url, t._test, _1); + return CheckedUnaryType(t.name, t.url, t.test.bind(t), _1); } // fromUncheckedBinaryType :: ((Type, Type) -> Type) -> @@ -2504,7 +2552,7 @@ var t = typeConstructor(Unknown, Unknown); var _1 = t.types.$1.extractor; var _2 = t.types.$2.extractor; - return CheckedBinaryType(t.name, t.url, t._test, _1, _2); + return CheckedBinaryType(t.name, t.url, t.test.bind(t), _1, _2); } return { @@ -2528,6 +2576,7 @@ NonZeroFiniteNumber: NonZeroFiniteNumber, NonZeroInteger: NonZeroInteger, NonZeroValidNumber: NonZeroValidNumber, + None: None, Null: Null, Nullable: fromUncheckedUnaryType(Nullable), Number: Number_, diff --git a/test/index.js b/test/index.js index 9716b4a..be3b194 100644 --- a/test/index.js +++ b/test/index.js @@ -69,6 +69,7 @@ var MIN_SAFE_INTEGER = -MAX_SAFE_INTEGER; var Integer = $.NullaryType( 'my-package/Integer', 'http://example.com/my-package#Integer', + $.Any, function(x) { return $.Number._test(x) && Math.floor(x) === Number(x) && @@ -3025,6 +3026,7 @@ describe('def', function() { var Void = $.NullaryType( 'my-package/Void', 'http://example.com/my-package#Void', + $.Any, function(x) { count += 1; return false; } ); @@ -3097,10 +3099,10 @@ describe('test', function() { describe('NullaryType', function() { - it('is a ternary function', function() { + it('is a quaternary function', function() { eq(typeof $.NullaryType, 'function'); - eq($.NullaryType.length, 3); - eq($.NullaryType.toString(), 'NullaryType :: String -> String -> (Any -> Boolean) -> Type'); + eq($.NullaryType.length, 4); + eq($.NullaryType.toString(), 'NullaryType :: String -> String -> Type -> (Any -> Boolean) -> Type'); }); }); From e983ea251bd8e26dd067d65d4974afd1f86ba2af Mon Sep 17 00:00:00 2001 From: Aldwin Vlasblom Date: Tue, 27 Jun 2017 11:00:12 +0200 Subject: [PATCH 02/20] Reorder types based on new ordering scheme The sheme has been discussed in #162 --- index.js | 365 +++++++++++++++++++++++++++---------------------------- 1 file changed, 180 insertions(+), 185 deletions(-) diff --git a/index.js b/index.js index afdf9c3..50838d3 100644 --- a/index.js +++ b/index.js @@ -466,15 +466,6 @@ Any.test = K(true); Any.validate = Right; - //# None :: Type - //. - //. Type with no members. - var None = NullaryTypeWithUrl('sanctuary-def/None', Any, K(false)); - - // Inconsistent :: Type - var Inconsistent = - new _Type(INCONSISTENT, '', '', always2('???'), None, K(false), [], {}); - //# AnyFunction :: Type //. //. Type comprising every Function value. @@ -500,15 +491,34 @@ //. Type comprising every Date value. var Date_ = NullaryTypeWithUrl('Date', Any, typeEq('Date')); + //# ValidDate :: Type + //. + //. Type comprising every [`Date`][] value except `new Date(NaN)`. + var ValidDate = NullaryTypeWithUrl( + 'sanctuary-def/ValidDate', + Date_, + function(x) { return !isNaN(x.valueOf()); } + ); + //# Error :: Type //. //. Type comprising every Error value, including values of more specific //. constructors such as [`SyntaxError`][] and [`TypeError`][]. var Error_ = NullaryTypeWithUrl('Error', Any, typeEq('Error')); + // Inconsistent :: Type + var Inconsistent = + new _Type(INCONSISTENT, '', '', always2('???'), Any, K(false), [], {}); + + //# Null :: Type + //. + //. Type whose sole member is `null`. + var Null = NullaryTypeWithUrl('Null', Any, typeEq('Null')); + //# Number :: Type //. - //. Type comprising every primitive Number value (including `NaN`). + //. Type comprising every primitive Number value + //. (including `NaN` and `Infinity`). var Number_ = NullaryTypeWithUrl('Number', Any, typeofEq('number')); //# ValidNumber :: Type @@ -530,62 +540,31 @@ isFinite ); - //# Function :: Array Type -> Type - //. - //. Constructor for Function types. - //. - //. Examples: + //# NegativeFiniteNumber :: Type //. - //. - `$.Function([$.Date, $.String])` represents the `Date -> String` - //. type; and - //. - `$.Function([a, b, a])` represents the `(a, b) -> a` type. - function Function_(types) { - function format(outer, inner) { - var xs = types.map(function(t, idx) { - return unless(t.type === RECORD || isEmpty(t.keys), - stripOutermostParens, - inner('$' + String(idx + 1))(String(t))); - }); - var parenthesize = wrap(outer('('))(outer(')')); - return parenthesize(unless(types.length === 2, - parenthesize, - init(xs).join(outer(', '))) + - outer(' -> ') + - last(xs)); - } - - var $keys = []; - var $types = {}; - types.forEach(function(t, idx) { - var k = '$' + String(idx + 1); - $keys.push(k); - $types[k] = {extractor: K([]), type: t}; - }); - - return new _Type(FUNCTION, - '', - '', - format, - AnyFunction, - K(true), - $keys, - $types); - } + //. Type comprising every [`FiniteNumber`][] less than zero. + var NegativeFiniteNumber = NullaryTypeWithUrl( + 'sanctuary-def/NegativeFiniteNumber', + FiniteNumber, + function(x) { return x < 0; } + ); - //# RegExp :: Type + //# NonZeroFiniteNumber :: Type //. - //. Type comprising every RegExp value. - var RegExp_ = NullaryTypeWithUrl('RegExp', Any, typeEq('RegExp')); + //. Type comprising every [`FiniteNumber`][] value except `0` and `-0`. + var NonZeroFiniteNumber = NullaryTypeWithUrl( + 'sanctuary-def/NonZeroFiniteNumber', + FiniteNumber, + function(x) { return x !== 0; } + ); - //# GlobalRegExp :: Type - //. - //. Type comprising every [`RegExp`][] value whose `global` flag is `true`. + //# PositiveFiniteNumber :: Type //. - //. See also [`NonGlobalRegExp`][]. - var GlobalRegExp = NullaryTypeWithUrl( - 'sanctuary-def/GlobalRegExp', - RegExp_, - function(x) { return x.global; } + //. Type comprising every [`FiniteNumber`][] value greater than zero. + var PositiveFiniteNumber = NullaryTypeWithUrl( + 'sanctuary-def/PositiveFiniteNumber', + FiniteNumber, + function(x) { return x > 0; } ); //# Integer :: Type @@ -602,15 +581,6 @@ } ); - //# NegativeFiniteNumber :: Type - //. - //. Type comprising every [`FiniteNumber`][] value less than zero. - var NegativeFiniteNumber = NullaryTypeWithUrl( - 'sanctuary-def/NegativeFiniteNumber', - FiniteNumber, - function(x) { return x < 0; } - ); - //# NegativeInteger :: Type //. //. Type comprising every [`Integer`][] value less than zero. @@ -620,52 +590,6 @@ function(x) { return x < 0; } ); - //# NegativeNumber :: Type - //. - //. Type comprising every [`Number`][] value less than zero. - var NegativeNumber = NullaryTypeWithUrl( - 'sanctuary-def/NegativeNumber', - Number_, - function(x) { return x < 0; } - ); - - //# NonEmpty :: Type -> Type - //. - //. Constructor for non-empty types. `$.NonEmpty($.String)`, for example, is - //. the type comprising every [`String`][] value except `''`. - //. - //. The given type must satisfy the [Monoid][] and [Setoid][] specifications. - var NonEmpty = UnaryType( - 'sanctuary-def/NonEmpty', - functionUrl('NonEmpty'), - function(x) { - return Z.Monoid.test(x) && - Z.Setoid.test(x) && - !Z.equals(x, Z.empty(x.constructor)); - }, - function(monoid) { return [monoid]; } - ); - - //# NonGlobalRegExp :: Type - //. - //. Type comprising every [`RegExp`][] value whose `global` flag is `false`. - //. - //. See also [`GlobalRegExp`][]. - var NonGlobalRegExp = NullaryTypeWithUrl( - 'sanctuary-def/NonGlobalRegExp', - RegExp_, - function(x) { return !x.global; } - ); - - //# NonZeroFiniteNumber :: Type - //. - //. Type comprising every [`FiniteNumber`][] value except `0` and `-0`. - var NonZeroFiniteNumber = NullaryTypeWithUrl( - 'sanctuary-def/NonZeroFiniteNumber', - FiniteNumber, - function(x) { return x !== 0; } - ); - //# NonZeroInteger :: Type //. //. Type comprising every [`Integer`][] value except `0` and `-0`. @@ -675,6 +599,24 @@ function(x) { return x !== 0; } ); + //# PositiveInteger :: Type + //. + //. Type comprising every [`Integer`][] value greater than zero. + var PositiveInteger = NullaryTypeWithUrl( + 'sanctuary-def/PositiveInteger', + Integer, + function(x) { return x > 0; } + ); + + //# NegativeNumber :: Type + //. + //. Type comprising every [`ValidNumber`][] value less than zero. + var NegativeNumber = NullaryTypeWithUrl( + 'sanctuary-def/NegativeNumber', + ValidNumber, + function(x) { return x < 0; } + ); + //# NonZeroValidNumber :: Type //. //. Type comprising every [`ValidNumber`][] value except `0` and `-0`. @@ -684,21 +626,13 @@ function(x) { return x !== 0; } ); - //# Null :: Type - //. - //. Type whose sole member is `null`. - var Null = NullaryTypeWithUrl('Null', Any, typeEq('Null')); - - //# Nullable :: Type -> Type + //# PositiveNumber :: Type //. - //. Constructor for types which include `null` as a member. - var Nullable = UnaryTypeWithUrl( - 'sanctuary-def/Nullable', - K(true), - function(nullable) { - // eslint-disable-next-line eqeqeq - return nullable === null ? [] : [nullable]; - } + //. Type comprising every [`ValidNumber`][] value greater than zero. + var PositiveNumber = NullaryTypeWithUrl( + 'sanctuary-def/PositiveNumber', + ValidNumber, + function(x) { return x > 0; } ); //# Object :: Type @@ -712,44 +646,6 @@ //. constructor function. var Object_ = NullaryTypeWithUrl('Object', Any, typeEq('Object')); - //# Pair :: Type -> Type -> Type - //. - //. Constructor for tuple types of length 2. Arrays are said to represent - //. tuples. `['foo', 42]` is a member of `Pair String Number`. - var Pair = BinaryTypeWithUrl( - 'sanctuary-def/Pair', - function(x) { return typeEq('Array')(x) && x.length === 2; }, - function(pair) { return [pair[0]]; }, - function(pair) { return [pair[1]]; } - ); - - //# PositiveFiniteNumber :: Type - //. - //. Type comprising every [`FiniteNumber`][] value greater than zero. - var PositiveFiniteNumber = NullaryTypeWithUrl( - 'sanctuary-def/PositiveFiniteNumber', - FiniteNumber, - function(x) { return x > 0; } - ); - - //# PositiveInteger :: Type - //. - //. Type comprising every [`Integer`][] value greater than zero. - var PositiveInteger = NullaryTypeWithUrl( - 'sanctuary-def/PositiveInteger', - Integer, - function(x) { return x > 0; } - ); - - //# PositiveNumber :: Type - //. - //. Type comprising every [`Number`][] value greater than zero. - var PositiveNumber = NullaryTypeWithUrl( - 'sanctuary-def/PositiveNumber', - Number_, - function(x) { return x > 0; } - ); - //# RegexFlags :: Type //. //. Type comprising the canonical RegExp flags: @@ -767,18 +663,31 @@ ['', 'g', 'i', 'm', 'gi', 'gm', 'im', 'gim'] ); - //# StrMap :: Type -> Type + //# RegExp :: Type //. - //. Constructor for homogeneous Object types. + //. Type comprising every RegExp value. + var RegExp_ = NullaryTypeWithUrl('RegExp', Any, typeEq('RegExp')); + + //# GlobalRegExp :: Type //. - //. `{foo: 1, bar: 2, baz: 3}`, for example, is a member of `StrMap Number`; - //. `{foo: 1, bar: 2, baz: 'XXX'}` is not. - var StrMap = UnaryTypeWithUrl( - 'sanctuary-def/StrMap', - Object_.test.bind(Object_), - function(strMap) { - return Z.reduce(function(xs, x) { return xs.concat([x]); }, [], strMap); - } + //. Type comprising every [`RegExp`][] value whose `global` flag is `true`. + //. + //. See also [`NonGlobalRegExp`][]. + var GlobalRegExp = NullaryTypeWithUrl( + 'sanctuary-def/GlobalRegExp', + RegExp_, + function(x) { return x.global; } + ); + + //# NonGlobalRegExp :: Type + //. + //. Type comprising every [`RegExp`][] value whose `global` flag is `false`. + //. + //. See also [`GlobalRegExp`][]. + var NonGlobalRegExp = NullaryTypeWithUrl( + 'sanctuary-def/NonGlobalRegExp', + RegExp_, + function(x) { return !x.global; } ); //# String :: Type @@ -829,13 +738,100 @@ var Unknown = new _Type(UNKNOWN, '', '', always2('???'), Any, K(true), [], {}); - //# ValidDate :: Type + //# Function :: Array Type -> Type //. - //. Type comprising every [`Date`][] value except `new Date(NaN)`. - var ValidDate = NullaryTypeWithUrl( - 'sanctuary-def/ValidDate', - Date_, - function(x) { return !isNaN(x.valueOf()); } + //. Constructor for Function types. + //. + //. Examples: + //. + //. - `$.Function([$.Date, $.String])` represents the `Date -> String` + //. type; and + //. - `$.Function([a, b, a])` represents the `(a, b) -> a` type. + function Function_(types) { + function format(outer, inner) { + var xs = types.map(function(t, idx) { + return unless(t.type === RECORD || isEmpty(t.keys), + stripOutermostParens, + inner('$' + String(idx + 1))(String(t))); + }); + var parenthesize = wrap(outer('('))(outer(')')); + return parenthesize(unless(types.length === 2, + parenthesize, + init(xs).join(outer(', '))) + + outer(' -> ') + + last(xs)); + } + + var $keys = []; + var $types = {}; + types.forEach(function(t, idx) { + var k = '$' + String(idx + 1); + $keys.push(k); + $types[k] = {extractor: K([]), type: t}; + }); + + return new _Type(FUNCTION, + '', + '', + format, + AnyFunction, + K(true), + $keys, + $types); + } + + //# NonEmpty :: Type -> Type + //. + //. Constructor for non-empty types. `$.NonEmpty($.String)`, for example, is + //. the type comprising every [`String`][] value except `''`. + //. + //. The given type must satisfy the [Monoid][] and [Setoid][] specifications. + var NonEmpty = UnaryType( + 'sanctuary-def/NonEmpty', + functionUrl('NonEmpty'), + function(x) { + return Z.Monoid.test(x) && + Z.Setoid.test(x) && + !Z.equals(x, Z.empty(x.constructor)); + }, + function(monoid) { return [monoid]; } + ); + + //# Nullable :: Type -> Type + //. + //. Constructor for types which include `null` as a member. + var Nullable = UnaryTypeWithUrl( + 'sanctuary-def/Nullable', + K(true), + function(nullable) { + // eslint-disable-next-line eqeqeq + return nullable === null ? [] : [nullable]; + } + ); + + //# Pair :: Type -> Type -> Type + //. + //. Constructor for tuple types of length 2. Arrays are said to represent + //. tuples. `['foo', 42]` is a member of `Pair String Number`. + var Pair = BinaryTypeWithUrl( + 'sanctuary-def/Pair', + function(x) { return typeEq('Array')(x) && x.length === 2; }, + function(pair) { return [pair[0]]; }, + function(pair) { return [pair[1]]; } + ); + + //# StrMap :: Type -> Type + //. + //. Constructor for homogeneous Object types. + //. + //. `{foo: 1, bar: 2, baz: 3}`, for example, is a member of `StrMap Number`; + //. `{foo: 1, bar: 2, baz: 'XXX'}` is not. + var StrMap = UnaryTypeWithUrl( + 'sanctuary-def/StrMap', + Object_.test.bind(Object_), + function(strMap) { + return Z.reduce(function(xs, x) { return xs.concat([x]); }, [], strMap); + } ); //# env :: Array Type @@ -2576,7 +2572,6 @@ NonZeroFiniteNumber: NonZeroFiniteNumber, NonZeroInteger: NonZeroInteger, NonZeroValidNumber: NonZeroValidNumber, - None: None, Null: Null, Nullable: fromUncheckedUnaryType(Nullable), Number: Number_, From c5f11304e77efd53f28bbbaae9cccd975e3517c6 Mon Sep 17 00:00:00 2001 From: Aldwin Vlasblom Date: Tue, 27 Jun 2017 11:45:08 +0200 Subject: [PATCH 03/20] Add refinement support to UnaryType constructor --- index.js | 37 ++++++++++++++++++++++--------------- test/index.js | 7 ++++--- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/index.js b/index.js index 50838d3..e8d55f1 100644 --- a/index.js +++ b/index.js @@ -441,8 +441,8 @@ // UnaryTypeWithUrl :: // (String, Any -> Boolean, t a -> Array a) -> (Type -> Type) - function UnaryTypeWithUrl(name, test, _1) { - return UnaryType(name, functionUrl(name), test, _1); + function UnaryTypeWithUrl(name, parent, test, _1) { + return UnaryType(name, functionUrl(name), parent, test, _1); } // BinaryTypeWithUrl :: @@ -476,11 +476,6 @@ //. Type comprising every [`arguments`][arguments] object. var Arguments = NullaryTypeWithUrl('Arguments', Any, typeEq('Arguments')); - //# Array :: Type -> Type - //. - //. Constructor for homogeneous Array types. - var Array_ = UnaryTypeWithUrl('Array', typeEq('Array'), id); - //# Boolean :: Type //. //. Type comprising `true` and `false`. @@ -738,6 +733,11 @@ var Unknown = new _Type(UNKNOWN, '', '', always2('???'), Any, K(true), [], {}); + //# Array :: Type -> Type + //. + //. Constructor for homogeneous Array types. + var Array_ = UnaryTypeWithUrl('Array', Any, typeEq('Array'), id); + //# Function :: Array Type -> Type //. //. Constructor for Function types. @@ -789,6 +789,7 @@ var NonEmpty = UnaryType( 'sanctuary-def/NonEmpty', functionUrl('NonEmpty'), + Any, function(x) { return Z.Monoid.test(x) && Z.Setoid.test(x) && @@ -802,6 +803,7 @@ //. Constructor for types which include `null` as a member. var Nullable = UnaryTypeWithUrl( 'sanctuary-def/Nullable', + Any, K(true), function(nullable) { // eslint-disable-next-line eqeqeq @@ -828,7 +830,8 @@ //. `{foo: 1, bar: 2, baz: 'XXX'}` is not. var StrMap = UnaryTypeWithUrl( 'sanctuary-def/StrMap', - Object_.test.bind(Object_), + Object_, + K(true), function(strMap) { return Z.reduce(function(xs, x) { return xs.concat([x]); }, [], strMap); } @@ -1393,7 +1396,7 @@ [String_, String_, Type, Function_([Any, Boolean_]), Type], NullaryType); - //# UnaryType :: String -> String -> (Any -> Boolean) -> (t a -> Array a) -> (Type -> Type) + //# UnaryType :: String -> String -> Type -> (Any -> Boolean) -> ((t a -> Array a) -> (Type -> Type)) //. //. Type constructor for types with one type variable (such as [`Array`][]). //. @@ -1403,6 +1406,8 @@ //. //. - the documentation URL of `t` (exposed as `t.url`); //. + //. - the parent of `t` (exposed as `t.parent`); + //. //. - a predicate which accepts any JavaScript value and returns `true` //. if (and only if) the value is a member of `t x` for some type `x`; //. @@ -1424,6 +1429,7 @@ //. const Maybe = $.UnaryType( //. maybeTypeIdent, //. 'http://example.com/my-package#Maybe', + //. $.Any, //. x => type(x) === maybeTypeIdent, //. maybe => maybe.isJust ? [maybe.value] : [] //. ); @@ -1471,14 +1477,14 @@ //. // //. // Since there is no type of which all the above values are members, the type-variable constraint has been violated. //. ``` - function UnaryType(name, url, test, _1) { + function UnaryType(name, url, parent, test, _1) { return function($1) { function format(outer, inner) { return outer('(' + stripNamespace(name) + ' ') + inner('$1')(String($1)) + outer(')'); } var types = {$1: {extractor: _1, type: $1}}; - return new _Type(UNARY, name, url, format, Any, test, ['$1'], types); + return new _Type(UNARY, name, url, format, parent, test, ['$1'], types); }; } @@ -1487,19 +1493,20 @@ {}, [String_, String_, + Type, Function_([Any, Boolean_]), Function_([Unchecked('t a'), Array_(Unchecked('a'))]), AnyFunction], - function(name, url, test, _1) { + function(name, url, parent, test, _1) { return def(stripNamespace(name), {}, [Type, Type], - UnaryType(name, url, test, _1)); + UnaryType(name, url, parent, test, _1)); }); // fromUnaryType :: Type -> (Type -> Type) function fromUnaryType(t) { - return UnaryType(t.name, t.url, t.test.bind(t), t.types.$1.extractor); + return UnaryType(t.name, t.url, t.parent, t._test, t.types.$1.extractor); } //# BinaryType :: String -> String -> (Any -> Boolean) -> (t a b -> Array a) -> (t a b -> Array b) -> (Type -> Type -> Type) @@ -2539,7 +2546,7 @@ function fromUncheckedUnaryType(typeConstructor) { var t = typeConstructor(Unknown); var _1 = t.types.$1.extractor; - return CheckedUnaryType(t.name, t.url, t.test.bind(t), _1); + return CheckedUnaryType(t.name, t.url, t.parent, t._test, _1); } // fromUncheckedBinaryType :: ((Type, Type) -> Type) -> diff --git a/test/index.js b/test/index.js index be3b194..271eb3d 100644 --- a/test/index.js +++ b/test/index.js @@ -131,6 +131,7 @@ function Just(x) { return new _Maybe('Just', x); } var Maybe = $.UnaryType( 'my-package/Maybe', 'http://example.com/my-package#Maybe', + $.Any, function(x) { return type(x) === 'my-package/Maybe'; }, function(maybe) { return maybe.isJust ? [maybe.value] : []; } ); @@ -3109,10 +3110,10 @@ describe('NullaryType', function() { describe('UnaryType', function() { - it('is a quaternary function', function() { + it('is a quinary function', function() { eq(typeof $.UnaryType, 'function'); - eq($.UnaryType.length, 4); - eq($.UnaryType.toString(), 'UnaryType :: String -> String -> (Any -> Boolean) -> (t a -> Array a) -> Function'); + eq($.UnaryType.length, 5); + eq($.UnaryType.toString(), 'UnaryType :: String -> String -> Type -> (Any -> Boolean) -> (t a -> Array a) -> Function'); }); it('returns a type constructor which type checks its arguments', function() { From 90b7aa3bcc4af2623e238bc15e7e748cab1c6ac3 Mon Sep 17 00:00:00 2001 From: Aldwin Vlasblom Date: Thu, 29 Jun 2017 13:34:30 +0200 Subject: [PATCH 04/20] Add refinement support to BinaryType constructor --- index.js | 30 ++++++++++++++++-------------- test/index.js | 11 +++++++---- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/index.js b/index.js index e8d55f1..9e59b22 100644 --- a/index.js +++ b/index.js @@ -370,10 +370,6 @@ _Type['@@type'] = 'sanctuary-def/Type'; - _Type.prototype.test = function(x) { - return this.parent.test(x) && this._test(x); - }; - _Type.prototype.validate = function(x) { var self = this; return Z.chain(function(x) { @@ -448,8 +444,8 @@ // BinaryTypeWithUrl :: // (String, Any -> Boolean, t a b -> Array a, t a b -> Array b) -> // ((Type, Type) -> Type) - function BinaryTypeWithUrl(name, test, _1, _2) { - return BinaryType(name, functionUrl(name), test, _1, _2); + function BinaryTypeWithUrl(name, parent, test, _1, _2) { + return BinaryType(name, functionUrl(name), parent, test, _1, _2); } //. ### Types @@ -817,7 +813,8 @@ //. tuples. `['foo', 42]` is a member of `Pair String Number`. var Pair = BinaryTypeWithUrl( 'sanctuary-def/Pair', - function(x) { return typeEq('Array')(x) && x.length === 2; }, + Array_(Any), + function(x) { return x.length === 2; }, function(pair) { return [pair[0]]; }, function(pair) { return [pair[1]]; } ); @@ -1509,7 +1506,7 @@ return UnaryType(t.name, t.url, t.parent, t._test, t.types.$1.extractor); } - //# BinaryType :: String -> String -> (Any -> Boolean) -> (t a b -> Array a) -> (t a b -> Array b) -> (Type -> Type -> Type) + //# BinaryType :: String -> String -> Type -> (Any -> Boolean) -> (t a b -> Array a) -> (t a b -> Array b) -> (Type -> Type -> Type) //. //. Type constructor for types with two type variables (such as [`Pair`][]). //. @@ -1519,6 +1516,8 @@ //. //. - the documentation URL of `t` (exposed as `t.url`); //. + //. - the parent of `t` (exposed as `t.parent`); + //. //. - a predicate which accepts any JavaScript value and returns `true` //. if (and only if) the value is a member of `t x y` for some types //. `x` and `y`; @@ -1547,6 +1546,7 @@ //. const $Pair = $.BinaryType( //. pairTypeIdent, //. 'http://example.com/my-package#Pair', + //. Any, //. x => type(x) === pairTypeIdent, //. pair => [pair[0]], //. pair => [pair[1]] @@ -1601,7 +1601,7 @@ //. // //. // The value at position 1 is not a member of ‘Rank’. //. ``` - function BinaryType(name, url, test, _1, _2) { + function BinaryType(name, url, parent, test, _1, _2) { return function($1, $2) { function format(outer, inner) { return outer('(' + stripNamespace(name) + ' ') + @@ -1614,7 +1614,7 @@ name, url, format, - Any, + parent, test, ['$1', '$2'], types); @@ -1626,22 +1626,24 @@ {}, [String_, String_, + Type, Function_([Any, Boolean_]), Function_([Unchecked('t a b'), Array_(Unchecked('a'))]), Function_([Unchecked('t a b'), Array_(Unchecked('b'))]), AnyFunction], - function(name, url, test, _1, _2) { + function(name, url, parent, test, _1, _2) { return def(stripNamespace(name), {}, [Type, Type, Type], - BinaryType(name, url, test, _1, _2)); + BinaryType(name, url, parent, test, _1, _2)); }); // xprod :: (Type, Array Type, Array Type) -> Array Type function xprod(t, $1s, $2s) { var specialize = BinaryType(t.name, t.url, - t.test.bind(t), + t.parent, + t._test, t.types.$1.extractor, t.types.$2.extractor); var $types = []; @@ -2555,7 +2557,7 @@ var t = typeConstructor(Unknown, Unknown); var _1 = t.types.$1.extractor; var _2 = t.types.$2.extractor; - return CheckedBinaryType(t.name, t.url, t.test.bind(t), _1, _2); + return CheckedBinaryType(t.name, t.url, t.parent, t._test, _1, _2); } return { diff --git a/test/index.js b/test/index.js index 271eb3d..6fe8de2 100644 --- a/test/index.js +++ b/test/index.js @@ -182,6 +182,7 @@ function Right(x) { return new _Either('Right', x); } var Either = $.BinaryType( 'my-package/Either', 'http://example.com/my-package#Either', + $.Any, function(x) { return type(x) === 'my-package/Either'; }, function(either) { return either.isLeft ? [either.value] : []; }, function(either) { return either.isRight ? [either.value] : []; } @@ -220,6 +221,7 @@ Pair.prototype.toString = function() { var $Pair = $.BinaryType( 'my-package/Pair', 'http://example.com/my-package#Pair', + $.Any, function(x) { return type(x) === 'my-package/Pair'; }, function(pair) { return [pair[0]]; }, function(pair) { return [pair[1]]; } @@ -2477,7 +2479,8 @@ describe('def', function() { var Pair = $.BinaryType( 'my-package/Pair', 'http://example.com/my-package#Pair', - function(x) { return Object.prototype.toString.call(x) === '[object Array]' && x.length === 2; }, + $.Array($.Any), + function(x) { return x.length === 2; }, function(pair) { return [pair[0]]; }, function(pair) { return [pair[1]]; } ); @@ -3136,10 +3139,10 @@ describe('UnaryType', function() { describe('BinaryType', function() { - it('is a quinary function', function() { + it('is a sexternary function', function() { eq(typeof $.BinaryType, 'function'); - eq($.BinaryType.length, 5); - eq($.BinaryType.toString(), 'BinaryType :: String -> String -> (Any -> Boolean) -> (t a b -> Array a) -> (t a b -> Array b) -> Function'); + eq($.BinaryType.length, 6); + eq($.BinaryType.toString(), 'BinaryType :: String -> String -> Type -> (Any -> Boolean) -> (t a b -> Array a) -> (t a b -> Array b) -> Function'); }); it('returns a type constructor which type checks its arguments', function() { From fa55a1046d62aff3bb6aa2693c4dae2ceeaeddb2 Mon Sep 17 00:00:00 2001 From: Aldwin Vlasblom Date: Thu, 29 Jun 2017 13:58:27 +0200 Subject: [PATCH 05/20] Remove no longer necessary null-check --- index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/index.js b/index.js index 9e59b22..213c3b2 100644 --- a/index.js +++ b/index.js @@ -1747,8 +1747,7 @@ } function test(x) { - return x != null && - keys.every(function(k) { return hasOwnProperty.call(x, k); }); + return keys.every(function(k) { return hasOwnProperty.call(x, k); }); } var $types = {}; From 75a55dd76920dde634d8e10d67d0fb41eb39d401 Mon Sep 17 00:00:00 2001 From: Aldwin Vlasblom Date: Thu, 29 Jun 2017 18:12:08 +0200 Subject: [PATCH 06/20] Incorporate PR feedback --- index.js | 44 +++++++++++++++++++++++++------------------- test/index.js | 2 +- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/index.js b/index.js index 213c3b2..2ee0405 100644 --- a/index.js +++ b/index.js @@ -435,16 +435,24 @@ return EnumType(name, functionUrl(name), members); } - // UnaryTypeWithUrl :: - // (String, Any -> Boolean, t a -> Array a) -> (Type -> Type) - function UnaryTypeWithUrl(name, parent, test, _1) { + // UnaryTypeWithUrl :: ... -> (Type -> Type) + function UnaryTypeWithUrl( + name, // :: String + parent, // :: Type + test, // :: Any -> Boolean + _1 // :: t a -> Array a + ) { return UnaryType(name, functionUrl(name), parent, test, _1); } - // BinaryTypeWithUrl :: - // (String, Any -> Boolean, t a b -> Array a, t a b -> Array b) -> - // ((Type, Type) -> Type) - function BinaryTypeWithUrl(name, parent, test, _1, _2) { + // BinaryTypeWithUrl :: ... -> ((Type, Type) -> Type) + function BinaryTypeWithUrl( + name, // :: String + parent, // :: Type + test, // :: Any -> Boolean + _1, // :: t a b -> Array a + _2 // :: t a b -> Array b + ) { return BinaryType(name, functionUrl(name), parent, test, _1, _2); } @@ -1303,9 +1311,9 @@ //. The environment is only significant if the type contains //. [type variables][]. //. - //. Using types as predicates is powerful. One could, for example, - //. define a [record type][] for each endpoint of a REST API and - //. validate the bodies of incoming POST requests against these types. + //. Using types as predicates is powerful. One could, for example, define a + //. [record type][] for each endpoint of a REST API and validate the bodies + //. of incoming POST requests against these types. function test(env, t, x) { var typeInfo = {name: 'name', constraints: {}, types: [t]}; return satisfactoryTypes(env, typeInfo, {}, t, 0, [], [x]).isRight; @@ -1325,7 +1333,7 @@ //. //. - the documentation URL of `t` (exposed as `t.url`); and //. - //. - the parent of `t` (exposed as `t.parent`); + //. - the parent type of `t` (exposed as `t.parent`); //. //. - a predicate which accepts any JavaScript value and returns `true` if //. (and only if) the value is a member of `t`. @@ -1393,7 +1401,7 @@ [String_, String_, Type, Function_([Any, Boolean_]), Type], NullaryType); - //# UnaryType :: String -> String -> Type -> (Any -> Boolean) -> ((t a -> Array a) -> (Type -> Type)) + //# UnaryType :: String -> String -> Type -> (Any -> Boolean) -> (t a -> Array a) -> (Type -> Type) //. //. Type constructor for types with one type variable (such as [`Array`][]). //. @@ -1403,7 +1411,7 @@ //. //. - the documentation URL of `t` (exposed as `t.url`); //. - //. - the parent of `t` (exposed as `t.parent`); + //. - the parent type of `t` (exposed as `t.parent`); //. //. - a predicate which accepts any JavaScript value and returns `true` //. if (and only if) the value is a member of `t x` for some type `x`; @@ -1516,7 +1524,7 @@ //. //. - the documentation URL of `t` (exposed as `t.url`); //. - //. - the parent of `t` (exposed as `t.parent`); + //. - the parent type of `t` (exposed as `t.parent`); //. //. - a predicate which accepts any JavaScript value and returns `true` //. if (and only if) the value is a member of `t x y` for some types @@ -1608,8 +1616,6 @@ inner('$1')(String($1)) + outer(' ') + inner('$2')(String($2)) + outer(')'); } - var types = {$1: {extractor: _1, type: $1}, - $2: {extractor: _2, type: $2}}; return new _Type(BINARY, name, url, @@ -1617,7 +1623,8 @@ parent, test, ['$1', '$2'], - types); + {$1: {extractor: _1, type: $1}, + $2: {extractor: _2, type: $2}}); }; } @@ -1872,7 +1879,6 @@ function format(outer, inner) { return outer('(' + name + ' ') + inner('$1')(String($1)) + outer(')'); } - var types = {$1: {extractor: K([]), type: $1}}; return new _Type(VARIABLE, name, '', @@ -1880,7 +1886,7 @@ Any, K(true), ['$1'], - types); + {$1: {extractor: K([]), type: $1}}); }; } diff --git a/test/index.js b/test/index.js index 6fe8de2..ecd6e5a 100644 --- a/test/index.js +++ b/test/index.js @@ -3139,7 +3139,7 @@ describe('UnaryType', function() { describe('BinaryType', function() { - it('is a sexternary function', function() { + it('is a senary function', function() { eq(typeof $.BinaryType, 'function'); eq($.BinaryType.length, 6); eq($.BinaryType.toString(), 'BinaryType :: String -> String -> Type -> (Any -> Boolean) -> (t a b -> Array a) -> (t a b -> Array b) -> Function'); From aada3db81c41c2ce07a43e0095d2a90e45eb5109 Mon Sep 17 00:00:00 2001 From: Aldwin Vlasblom Date: Fri, 30 Jun 2017 11:27:30 +0200 Subject: [PATCH 07/20] Add refinement support to RecordType constructor Fixes #141 --- index.js | 26 +++++++++++++++----------- test/index.js | 18 +++++++++--------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/index.js b/index.js index 2ee0405..4ba6d10 100644 --- a/index.js +++ b/index.js @@ -1691,20 +1691,25 @@ var CheckedEnumType = def('EnumType', {}, [String_, String_, Array_(Any), Type], EnumType); - //# RecordType :: StrMap Type -> Type + //# RecordType :: Type -> StrMap Type -> Type //. //. `RecordType` is used to construct record types. The type definition //. specifies the name and type of each required field. //. //. To define a record type one must provide: //. + //. - the parent type of `t` (exposed as `t.parent`); + //. //. - an object mapping field name to type. //. //. For example: //. //. ```javascript //. // Point :: Type - //. const Point = $.RecordType({x: $.FiniteNumber, y: $.FiniteNumber}); + //. const Point = $.RecordType($.Any, { + //. x: $.FiniteNumber, + //. y: $.FiniteNumber + //. }); //. //. // dist :: Point -> Point -> FiniteNumber //. const dist = @@ -1740,7 +1745,7 @@ //. // //. // The value at position 1 is not a member of ‘{ x :: FiniteNumber, y :: FiniteNumber }’. //. ``` - function RecordType(fields) { + function RecordType(parent, fields) { var keys = Object.keys(fields).sort(); function format(outer, inner) { @@ -1754,7 +1759,9 @@ } function test(x) { - return keys.every(function(k) { return hasOwnProperty.call(x, k); }); + if (x == null) return false; + var o = Object(x); + return keys.every(function(k) { return k in o; }); } var $types = {}; @@ -1762,11 +1769,11 @@ $types[k] = {extractor: function(x) { return [x[k]]; }, type: fields[k]}; }); - return new _Type(RECORD, '', '', format, Object_, test, keys, $types); + return new _Type(RECORD, '', '', format, parent, test, keys, $types); } var CheckedRecordType = - def('RecordType', {}, [StrMap(Type), Type], RecordType); + def('RecordType', {}, [Type, StrMap(Type), Type], RecordType); //# TypeVariable :: String -> Type //. @@ -2543,11 +2550,8 @@ def); } - var create = - def('create', - {}, - [RecordType({checkTypes: Boolean_, env: Array_(Any)}), AnyFunction], - _create); + var Options = RecordType(Object_, {checkTypes: Boolean_, env: Array_(Any)}); + var create = def('create', {}, [Options, AnyFunction], _create); // fromUncheckedUnaryType :: (Type -> Type) -> (Type -> Type) function fromUncheckedUnaryType(typeConstructor) { diff --git a/test/index.js b/test/index.js index ecd6e5a..a7ea0c8 100644 --- a/test/index.js +++ b/test/index.js @@ -1204,14 +1204,14 @@ describe('def', function() { it('supports record types', function() { eq(typeof $.RecordType, 'function'); - eq($.RecordType.length, 1); - eq($.RecordType.toString(), 'RecordType :: StrMap Type -> Type'); + eq($.RecordType.length, 2); + eq($.RecordType.toString(), 'RecordType :: Type -> StrMap Type -> Type'); // Point :: Type - var Point = $.RecordType({x: $.Number, y: $.Number}); + var Point = $.RecordType($.Any, {x: $.Number, y: $.Number}); // Line :: Type - var Line = $.RecordType({start: Point, end: Point}); + var Line = $.RecordType($.Any, {start: Point, end: Point}); // dist :: Point -> Point -> Number var dist = def('dist', {}, [Point, Point, $.Number], function(p, q) { @@ -1312,13 +1312,13 @@ describe('def', function() { eq(id([{x: 0, y: 0}, {x: 1, y: 1}]), [{x: 0, y: 0}, {x: 1, y: 1}]); - throws(function() { $.RecordType({x: /XXX/, y: /XXX/, z: $.Any}); }, + throws(function() { $.RecordType($.Any, {x: /XXX/, y: /XXX/, z: $.Any}); }, TypeError, 'Invalid value\n' + '\n' + - 'RecordType :: StrMap Type -> Type\n' + - ' ^^^^\n' + - ' 1\n' + + 'RecordType :: Type -> StrMap Type -> Type\n' + + ' ^^^^\n' + + ' 1\n' + '\n' + '1) /XXX/ :: RegExp\n' + '\n' + @@ -1327,7 +1327,7 @@ describe('def', function() { 'See https://github.com/sanctuary-js/sanctuary-def/tree/v' + version + '#Type for information about the Type type.\n'); // Foo :: Type - var Foo = $.RecordType({x: a, y: a}); + var Foo = $.RecordType($.Any, {x: a, y: a}); // foo :: Foo -> Foo var foo = def('foo', {}, [Foo, Foo], identity); From 96d562c8839fe072ac9853bfd10c94193f49e68a Mon Sep 17 00:00:00 2001 From: Aldwin Vlasblom Date: Fri, 30 Jun 2017 12:18:01 +0200 Subject: [PATCH 08/20] Document how to use record type refinement --- index.js | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 4ba6d10..7fbfa15 100644 --- a/index.js +++ b/index.js @@ -1720,9 +1720,6 @@ //. dist({x: 0, y: 0}, {x: 3, y: 4}); //. // => 5 //. - //. dist({x: 0, y: 0}, {x: 3, y: 4, color: 'red'}); - //. // => 5 - //. //. dist({x: 0, y: 0}, {x: NaN, y: NaN}); //. // ! TypeError: Invalid value //. // @@ -1745,6 +1742,57 @@ //. // //. // The value at position 1 is not a member of ‘{ x :: FiniteNumber, y :: FiniteNumber }’. //. ``` + //. + //. ##### Extending record types + //. + //. By using the `parent` argument of the RecordType constructor, we can + //. define record types which must contain all the same properties of + //. another. For example, we could define `Poinst3D` as a refinement of our + //. `Point` from the previous example: + //. + //. ```js + //. // Point3D :: Type + //. const Point3D = $.RecordType(Point, {z: $.FiniteNumber}); + //. + //. // a :: Point + //. const a = {x: 0, y: 0}; + //. + //. // b :: Point, Point3D + //. const b = {x: 3, y: 4, z: 5}; + //. + //. Point.validate(a) // => Right(a) + //. Point3D.validate(a) // => Left({propPath: ['z'], value: undefined}) + //. + //. Point.validate(b) // => Right(b) + //. Point3D.validate(b) // => Right(b) + //. + //. dist(a, b); + //. // => 5 + //. ``` + //. + //. We can see that `b` is a member of `Point3D` as well as `Point`, allowing + //. us to call `dist` on it. By default record types allow additional + //. properties, allowing for this "extension". If you wish for a record type + //. to allow no additional properties, we can refine it as such: + //. + //. ```js + //. // strict :: Type -> Type + //. const strict = t => $.NullaryType( + //. 'Strict' + t.name, + //. '', + //. t, + //. x => Object.keys(x).length === t.keys.length + //. ); + //. + //. // StrictPoint3D :: Type + //. const StrictPoint3D = strict(Point3D) + //. + //. // a :: Point, Point3D + //. const a = {x: 1, y: 2, z: 3, color: 'red'} + //. + //. Point3D.validate(a) // => Right(a) + //. StrictPoint3D.validate(a) // => Left({propPath: [], value: a}) + //. ``` function RecordType(parent, fields) { var keys = Object.keys(fields).sort(); From 7b0ac2c92ad3af121d5e79ac2b44418f89237fb8 Mon Sep 17 00:00:00 2001 From: Aldwin Vlasblom Date: Fri, 30 Jun 2017 12:26:40 +0200 Subject: [PATCH 09/20] Incorporate PR feedback --- index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 7fbfa15..b441515 100644 --- a/index.js +++ b/index.js @@ -1554,7 +1554,7 @@ //. const $Pair = $.BinaryType( //. pairTypeIdent, //. 'http://example.com/my-package#Pair', - //. Any, + //. $.Any, //. x => type(x) === pairTypeIdent, //. pair => [pair[0]], //. pair => [pair[1]] @@ -1708,7 +1708,7 @@ //. // Point :: Type //. const Point = $.RecordType($.Any, { //. x: $.FiniteNumber, - //. y: $.FiniteNumber + //. y: $.FiniteNumber, //. }); //. //. // dist :: Point -> Point -> FiniteNumber From 6d6960bbf04dabb2131f58554b01896cb288455e Mon Sep 17 00:00:00 2001 From: Aldwin Vlasblom Date: Fri, 30 Jun 2017 12:27:07 +0200 Subject: [PATCH 10/20] :warning: Change env from Array Any to Array Type --- index.js | 2 +- test/index.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index b441515..db69250 100644 --- a/index.js +++ b/index.js @@ -2598,7 +2598,7 @@ def); } - var Options = RecordType(Object_, {checkTypes: Boolean_, env: Array_(Any)}); + var Options = RecordType(Object_, {checkTypes: Boolean_, env: Array_(Type)}); var create = def('create', {}, [Options, AnyFunction], _create); // fromUncheckedUnaryType :: (Type -> Type) -> (Type -> Type) diff --git a/test/index.js b/test/index.js index a7ea0c8..1efea50 100644 --- a/test/index.js +++ b/test/index.js @@ -240,13 +240,13 @@ describe('create', function() { TypeError, 'Invalid value\n' + '\n' + - 'create :: { checkTypes :: Boolean, env :: Array Any } -> Function\n' + - ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' + + 'create :: { checkTypes :: Boolean, env :: Array Type } -> Function\n' + + ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' + ' 1\n' + '\n' + '1) true :: Boolean\n' + '\n' + - 'The value at position 1 is not a member of ‘{ checkTypes :: Boolean, env :: Array Any }’.\n'); + 'The value at position 1 is not a member of ‘{ checkTypes :: Boolean, env :: Array Type }’.\n'); }); }); From 02ea18c0e3ce256cf14d637c24be485b84ce2af0 Mon Sep 17 00:00:00 2001 From: Aldwin Vlasblom Date: Fri, 30 Jun 2017 12:33:34 +0200 Subject: [PATCH 11/20] Rephrase the documentation using "one" over "we" --- index.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index db69250..8d1bcdb 100644 --- a/index.js +++ b/index.js @@ -1745,9 +1745,9 @@ //. //. ##### Extending record types //. - //. By using the `parent` argument of the RecordType constructor, we can + //. By using the `parent` argument of the RecordType constructor, one can //. define record types which must contain all the same properties of - //. another. For example, we could define `Poinst3D` as a refinement of our + //. another. For example, one could define `Poinst3D` as a refinement of //. `Point` from the previous example: //. //. ```js @@ -1770,10 +1770,10 @@ //. // => 5 //. ``` //. - //. We can see that `b` is a member of `Point3D` as well as `Point`, allowing - //. us to call `dist` on it. By default record types allow additional - //. properties, allowing for this "extension". If you wish for a record type - //. to allow no additional properties, we can refine it as such: + //. The `b` value is a member of `Point3D` as well as `Point`, allowing one + //. to call `dist` on it. By default record types allow additional + //. properties, permitting record "extension". If one wishes for a record + //. type to allow for no additional properties, it can be refined as such: //. //. ```js //. // strict :: Type -> Type From 017a37660a07a1e106d1897e63e649750bd2ec0f Mon Sep 17 00:00:00 2001 From: Aldwin Vlasblom Date: Fri, 30 Jun 2017 13:00:53 +0200 Subject: [PATCH 12/20] Add assertions for record extension --- test/index.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/test/index.js b/test/index.js index 1efea50..e359aa0 100644 --- a/test/index.js +++ b/test/index.js @@ -1210,6 +1210,9 @@ describe('def', function() { // Point :: Type var Point = $.RecordType($.Any, {x: $.Number, y: $.Number}); + // Point3D :: Type + var Point3D = $.RecordType(Point, {z: $.Number}); + // Line :: Type var Line = $.RecordType($.Any, {start: Point, end: Point}); @@ -1224,12 +1227,17 @@ describe('def', function() { }); eq(dist({x: 0, y: 0}, {x: 0, y: 0}), 0); - eq(dist({x: 0, y: 0}, {x: 0, y: 0, color: 'red'}), 0); + eq(dist({x: 0, y: 0}, {x: 0, y: 0, z: 0}), 0); eq(dist({x: 1, y: 1}, {x: 4, y: 5}), 5); - eq(dist({x: 1, y: 1}, {x: 4, y: 5, color: 'red'}), 5); + eq(dist({x: 1, y: 1}, {x: 4, y: 5, z: 6}), 5); eq(length({start: {x: 1, y: 1}, end: {x: 4, y: 5}}), 5); - eq(length({start: {x: 1, y: 1}, end: {x: 4, y: 5, color: 'red'}}), 5); + eq(length({start: {x: 1, y: 1}, end: {x: 4, y: 5, z: 6}}), 5); + + eq($.test([], Point3D, {x: 0, y: 0}), false); + eq($.test([], Point3D, {x: 0, z: 0}), false); + eq($.test([], Point3D, {y: 0, z: 0}), false); + eq($.test([], Point3D, {x: 0, y: 0, z: 0}), true); throws(function() { dist(null); }, TypeError, From 1ae62f47053b4141ed34ca5fa05292296913079e Mon Sep 17 00:00:00 2001 From: Aldwin Vlasblom Date: Wed, 5 Jul 2017 11:34:20 +0200 Subject: [PATCH 13/20] Aleviate minor complaints about documentation --- index.js | 48 ++++++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/index.js b/index.js index 8d1bcdb..b15f227 100644 --- a/index.js +++ b/index.js @@ -1745,37 +1745,38 @@ //. //. ##### Extending record types //. - //. By using the `parent` argument of the RecordType constructor, one can + //. By using the `parent` argument of the `RecordType` constructor, one can //. define record types which must contain all the same properties of - //. another. For example, one could define `Poinst3D` as a refinement of + //. another. For example, one could define `Point3D` as a refinement of //. `Point` from the previous example: //. - //. ```js + //. ```javascript //. // Point3D :: Type //. const Point3D = $.RecordType(Point, {z: $.FiniteNumber}); //. - //. // a :: Point - //. const a = {x: 0, y: 0}; + //. // p1 :: Point + //. const p1 = {x: 0, y: 0}; //. - //. // b :: Point, Point3D - //. const b = {x: 3, y: 4, z: 5}; + //. // p2 :: Point, Point3D + //. const p2 = {x: 3, y: 4, z: 5}; //. - //. Point.validate(a) // => Right(a) - //. Point3D.validate(a) // => Left({propPath: ['z'], value: undefined}) + //. Point.validate(p1); // => Right({x: 0, y: 0}) + //. Point3D.validate(p1); // => Left({propPath: ['z'], value: undefined}) //. - //. Point.validate(b) // => Right(b) - //. Point3D.validate(b) // => Right(b) + //. Point.validate(p2); // => Right({x: 3, y: 4, z: 5}) + //. Point3D.validate(p2); // => Right({x: 3, y: 4, z: 5}) //. - //. dist(a, b); + //. dist(p1, p2); //. // => 5 //. ``` //. - //. The `b` value is a member of `Point3D` as well as `Point`, allowing one - //. to call `dist` on it. By default record types allow additional - //. properties, permitting record "extension". If one wishes for a record - //. type to allow for no additional properties, it can be refined as such: + //. The `p2` value is a member of `Point3D` as well as `Point`, making it a + //. valid argument to `dist`. By default record types allow additional + //. properties (which is what allowed `p2` to be a member of `Point`). If one + //. wishes for a record type to allow for no additional properties, it can be + //. refined as such: //. - //. ```js + //. ```javascript //. // strict :: Type -> Type //. const strict = t => $.NullaryType( //. 'Strict' + t.name, @@ -1785,13 +1786,16 @@ //. ); //. //. // StrictPoint3D :: Type - //. const StrictPoint3D = strict(Point3D) + //. const StrictPoint3D = strict(Point3D); + //. + //. // p :: Point, Point3D + //. const p = {x: 1, y: 2, z: 3, color: 'red'}; //. - //. // a :: Point, Point3D - //. const a = {x: 1, y: 2, z: 3, color: 'red'} + //. Point3D.validate(p); + //. // => Right({x: 1, y: 2, z: 3, color: 'red'}) //. - //. Point3D.validate(a) // => Right(a) - //. StrictPoint3D.validate(a) // => Left({propPath: [], value: a}) + //. StrictPoint3D.validate(p); + //. // => Left({propPath: [], value: {x: 1, y: 2, z: 3, color: 'red'}}) //. ``` function RecordType(parent, fields) { var keys = Object.keys(fields).sort(); From 4b906681eac53ca9af97e7b15c102e6c32385cb0 Mon Sep 17 00:00:00 2001 From: Aldwin Vlasblom Date: Wed, 5 Jul 2017 13:05:02 +0200 Subject: [PATCH 14/20] In-line Options record definition with create definition --- index.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index b15f227..1eb03d9 100644 --- a/index.js +++ b/index.js @@ -2602,8 +2602,12 @@ def); } - var Options = RecordType(Object_, {checkTypes: Boolean_, env: Array_(Type)}); - var create = def('create', {}, [Options, AnyFunction], _create); + var create = + def('create', + {}, + [RecordType(Object_, {checkTypes: Boolean_, env: Array_(Type)}), + AnyFunction], + _create); // fromUncheckedUnaryType :: (Type -> Type) -> (Type -> Type) function fromUncheckedUnaryType(typeConstructor) { From 9708db1800a0bc2c04b72b67ab1356489d124948 Mon Sep 17 00:00:00 2001 From: Aldwin Vlasblom Date: Wed, 5 Jul 2017 16:37:03 +0200 Subject: [PATCH 15/20] Add Strict type constructor --- index.js | 42 ++++++++++++++++++++++++++++-------------- test/index.js | 16 ++++++++++++++++ 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/index.js b/index.js index 1eb03d9..924277f 100644 --- a/index.js +++ b/index.js @@ -827,6 +827,23 @@ function(pair) { return [pair[1]]; } ); + //# Strict :: Type -> Type + //. + //. Constructor for strict record types. For example: + //. `$.Strict($.Record($.Any, {name: $.String}))`, is the type comprising + //. every object with a `name :: String` property, without any other + //. properties. + function Strict(t) { + function test(x) { + return Object.keys(x).length === t.keys.length; + } + + return NullaryType('sanctuary-def/Strict', + functionUrl('Strict'), + t, + test); + } + //# StrMap :: Type -> Type //. //. Constructor for homogeneous Object types. @@ -1774,27 +1791,22 @@ //. valid argument to `dist`. By default record types allow additional //. properties (which is what allowed `p2` to be a member of `Point`). If one //. wishes for a record type to allow for no additional properties, it can be - //. refined as such: + //. refined using the [`Strict`][] constructor: //. //. ```javascript - //. // strict :: Type -> Type - //. const strict = t => $.NullaryType( - //. 'Strict' + t.name, - //. '', - //. t, - //. x => Object.keys(x).length === t.keys.length - //. ); - //. //. // StrictPoint3D :: Type - //. const StrictPoint3D = strict(Point3D); + //. const StrictPoint3D = Strict(Point3D); //. - //. // p :: Point, Point3D - //. const p = {x: 1, y: 2, z: 3, color: 'red'}; + //. // p3 :: Point, Point3D + //. const p3 = {x: 1, y: 2, z: 3, color: 'red'}; //. - //. Point3D.validate(p); + //. Point3D.validate(p3); //. // => Right({x: 1, y: 2, z: 3, color: 'red'}) //. - //. StrictPoint3D.validate(p); + //. StrictPoint3D.validate(p2); + //. // => Right({x: 3, y: 4, z: 5}) + //. + //. StrictPoint3D.validate(p3); //. // => Left({propPath: [], value: {x: 1, y: 2, z: 3, color: 'red'}}) //. ``` function RecordType(parent, fields) { @@ -2657,6 +2669,7 @@ RegExp: RegExp_, RegexFlags: RegexFlags, StrMap: fromUncheckedUnaryType(StrMap), + Strict: Strict, String: String_, Symbol: Symbol_, Type: Type, @@ -2695,6 +2708,7 @@ //. [`Pair`]: #Pair //. [`RegExp`]: #RegExp //. [`RegexFlags`]: #RegexFlags +//. [`Strict`]: #Strict //. [`String`]: #String //. [`SyntaxError`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError //. [`TypeClass`]: https://github.com/sanctuary-js/sanctuary-type-classes#TypeClass diff --git a/test/index.js b/test/index.js index e359aa0..18ed6d5 100644 --- a/test/index.js +++ b/test/index.js @@ -1357,6 +1357,22 @@ describe('def', function() { 'Since there is no type of which all the above values are members, the type-variable constraint has been violated.\n'); }); + it('supports "strict" record types', function() { + + // Point :: Type + var Point = $.RecordType($.Any, {x: $.Number, y: $.Number}); + + // StrictPoint :: Type + var StrictPoint = $.Strict(Point); + + // p :: Point + var p = {x: 1, y: 2, z: 3}; + + eq($.test([], Point, p), true); + eq($.test([], StrictPoint, p), false); + + }); + it('supports "nullable" types', function() { eq(typeof $.Nullable, 'function'); eq($.Nullable.length, 1); From cc92cd2f3aed66dcea628d0a03a50d88b1b80aed Mon Sep 17 00:00:00 2001 From: Aldwin Vlasblom Date: Wed, 5 Jul 2017 16:50:15 +0200 Subject: [PATCH 16/20] Rephrase documentation on additional record fields --- index.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 924277f..b92be9e 100644 --- a/index.js +++ b/index.js @@ -1788,10 +1788,12 @@ //. ``` //. //. The `p2` value is a member of `Point3D` as well as `Point`, making it a - //. valid argument to `dist`. By default record types allow additional - //. properties (which is what allowed `p2` to be a member of `Point`). If one - //. wishes for a record type to allow for no additional properties, it can be - //. refined using the [`Strict`][] constructor: + //. valid argument to `dist`. By default, record types permit the presence + //. of additional fields. As a result, p2 is a member of Point as well as + //. Point3D. + //. + //. One could use the [`Strict`][] constructor to define record types which + //. do not permit additional fields: //. //. ```javascript //. // StrictPoint3D :: Type From 2fce7d96f78306eaa85fbf8b35f4052bf8022330 Mon Sep 17 00:00:00 2001 From: Aldwin Vlasblom Date: Wed, 5 Jul 2017 17:00:25 +0200 Subject: [PATCH 17/20] Incorporate PR feedback --- index.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index b92be9e..37397cc 100644 --- a/index.js +++ b/index.js @@ -829,10 +829,9 @@ //# Strict :: Type -> Type //. - //. Constructor for strict record types. For example: - //. `$.Strict($.Record($.Any, {name: $.String}))`, is the type comprising - //. every object with a `name :: String` property, without any other - //. properties. + //. Constructor for strict record types. + //. `$.Strict($.Record($.Any, {name: $.String}))`, for example, is the type + //. comprising every object with exactly one field, `name`, of type `String`. function Strict(t) { function test(x) { return Object.keys(x).length === t.keys.length; @@ -1788,9 +1787,9 @@ //. ``` //. //. The `p2` value is a member of `Point3D` as well as `Point`, making it a - //. valid argument to `dist`. By default, record types permit the presence - //. of additional fields. As a result, p2 is a member of Point as well as - //. Point3D. + //. valid argument to `dist`. By default, record types permit the presence of + //. additional fields. As a result, `p2` is a member of `Point` as well as + //. `Point3D`. //. //. One could use the [`Strict`][] constructor to define record types which //. do not permit additional fields: From f1da1c4b640dc4782984a3825de080a1d06a23d1 Mon Sep 17 00:00:00 2001 From: Aldwin Vlasblom Date: Wed, 5 Jul 2017 17:51:22 +0200 Subject: [PATCH 18/20] Take all enumerable properties into account for strict record types --- index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 37397cc..8a9bf45 100644 --- a/index.js +++ b/index.js @@ -834,7 +834,9 @@ //. comprising every object with exactly one field, `name`, of type `String`. function Strict(t) { function test(x) { - return Object.keys(x).length === t.keys.length; + var len = 0, o = Object(x); + for (var k in o) len += 1; // eslint-disable-line no-unused-vars + return t.keys.length === len; } return NullaryType('sanctuary-def/Strict', From e08290ca12b53ca0631b6961f45ac32cc7e09831 Mon Sep 17 00:00:00 2001 From: Aldwin Vlasblom Date: Mon, 10 Jul 2017 10:51:20 +0200 Subject: [PATCH 19/20] Let record and strict types acknowledge all enumerable properties --- index.js | 30 ++++++++++++++++-------------- test/index.js | 14 ++++++++++---- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/index.js b/index.js index 8a9bf45..694a8c6 100644 --- a/index.js +++ b/index.js @@ -260,6 +260,13 @@ }; } + // keys :: Any -> Array + function keys(x) { + var props = [], o = Object(x); + for (var prop in o) props.push(prop); + return props; + } + // last :: Array a -> a function last(xs) { return xs[xs.length - 1]; } @@ -833,16 +840,12 @@ //. `$.Strict($.Record($.Any, {name: $.String}))`, for example, is the type //. comprising every object with exactly one field, `name`, of type `String`. function Strict(t) { - function test(x) { - var len = 0, o = Object(x); - for (var k in o) len += 1; // eslint-disable-line no-unused-vars - return t.keys.length === len; - } - return NullaryType('sanctuary-def/Strict', functionUrl('Strict'), t, - test); + function test(x) { + return t.keys.length === keys(x).length; + }); } //# StrMap :: Type -> Type @@ -1813,7 +1816,7 @@ //. // => Left({propPath: [], value: {x: 1, y: 2, z: 3, color: 'red'}}) //. ``` function RecordType(parent, fields) { - var keys = Object.keys(fields).sort(); + var props = keys(fields).sort(); function format(outer, inner) { return wrap(outer('{'))(outer(' }'))(Z.map(function(k) { @@ -1822,21 +1825,20 @@ unless(t.type === RECORD || isEmpty(t.keys), stripOutermostParens, inner(k)(String(t))); - }, keys).join(outer(','))); + }, props).join(outer(','))); } function test(x) { - if (x == null) return false; - var o = Object(x); - return keys.every(function(k) { return k in o; }); + var actualProps = keys(x); + return props.every(function(k) { return actualProps.indexOf(k) >= 0; }); } var $types = {}; - keys.forEach(function(k) { + props.forEach(function(k) { $types[k] = {extractor: function(x) { return [x[k]]; }, type: fields[k]}; }); - return new _Type(RECORD, '', '', format, parent, test, keys, $types); + return new _Type(RECORD, '', '', format, parent, test, props, $types); } var CheckedRecordType = diff --git a/test/index.js b/test/index.js index 18ed6d5..8f0d3bb 100644 --- a/test/index.js +++ b/test/index.js @@ -1230,6 +1230,7 @@ describe('def', function() { eq(dist({x: 0, y: 0}, {x: 0, y: 0, z: 0}), 0); eq(dist({x: 1, y: 1}, {x: 4, y: 5}), 5); eq(dist({x: 1, y: 1}, {x: 4, y: 5, z: 6}), 5); + eq(dist(Object.assign(Object.create({x: 1}), {y: 1}), {x: 4, y: 5}), 5); eq(length({start: {x: 1, y: 1}, end: {x: 4, y: 5}}), 5); eq(length({start: {x: 1, y: 1}, end: {x: 4, y: 5, z: 6}}), 5); @@ -1365,11 +1366,16 @@ describe('def', function() { // StrictPoint :: Type var StrictPoint = $.Strict(Point); - // p :: Point - var p = {x: 1, y: 2, z: 3}; + // p1 :: Point + var p1 = {x: 1, y: 2, z: 3}; - eq($.test([], Point, p), true); - eq($.test([], StrictPoint, p), false); + // p2 :: Point, StrictPoint + var p2 = Object.assign(Object.create({x: 1}), {y: 2}); + + eq($.test([], Point, p1), true); + eq($.test([], StrictPoint, p1), false); + eq($.test([], Point, p2), true); + eq($.test([], StrictPoint, p2), true); }); From 7b57bc0a2bb974763b81833a1bce2554c0da2cfd Mon Sep 17 00:00:00 2001 From: Aldwin Vlasblom Date: Mon, 10 Jul 2017 13:02:01 +0200 Subject: [PATCH 20/20] WIP: Level Strict and NonEmpty in terms of approach --- index.js | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/index.js b/index.js index 694a8c6..d97e49e 100644 --- a/index.js +++ b/index.js @@ -797,17 +797,16 @@ //. the type comprising every [`String`][] value except `''`. //. //. The given type must satisfy the [Monoid][] and [Setoid][] specifications. - var NonEmpty = UnaryType( - 'sanctuary-def/NonEmpty', - functionUrl('NonEmpty'), - Any, - function(x) { + function NonEmpty(parent) { + function test(x) { return Z.Monoid.test(x) && Z.Setoid.test(x) && !Z.equals(x, Z.empty(x.constructor)); - }, - function(monoid) { return [monoid]; } - ); + } + function extract(monoid) { return [monoid]; } + var t = UnaryTypeWithUrl('sanctuary-def/NonEmpty', parent, test, extract); + return t(parent); + } //# Nullable :: Type -> Type //. @@ -839,13 +838,11 @@ //. Constructor for strict record types. //. `$.Strict($.Record($.Any, {name: $.String}))`, for example, is the type //. comprising every object with exactly one field, `name`, of type `String`. - function Strict(t) { - return NullaryType('sanctuary-def/Strict', - functionUrl('Strict'), - t, - function test(x) { - return t.keys.length === keys(x).length; - }); + function Strict(parent) { + function test(x) { return parent.keys.length === keys(x).length; } + function extract(record) { return [record]; } + var t = UnaryTypeWithUrl('sanctuary-def/Strict', parent, test, extract); + return t(parent); } //# StrMap :: Type -> Type