diff --git a/index.js b/index.js index 1d70c89..3c15ba1 100644 --- a/index.js +++ b/index.js @@ -251,6 +251,9 @@ // always2 :: a -> (b, c) -> a function always2(x) { return function(y, z) { return x; }; } + // complement :: (a -> Boolean) -> a -> Boolean + function complement(pred) { return function(x) { return !(pred (x)); }; } + // init :: Array a -> Array a function init(xs) { return xs.slice (0, -1); } @@ -286,6 +289,12 @@ // or :: (Array a, Array a) -> Array a function or(xs, ys) { return isEmpty (xs) ? ys : xs; } + // prop :: String -> {} -> a + function prop(field) { return function(record) { return record[field]; }; } + + // sizeEq :: Foldable f => Integer -> f a -> Boolean + function sizeEq(n) { return function(xs) { return Z.size (xs) === n; }; } + // strRepeat :: (String, Integer) -> String function strRepeat(s, times) { return joinWith (s, Array (times + 1)); @@ -353,20 +362,23 @@ return typeClass.name.slice (typeClass.name.indexOf ('/') + 1); } + function _test(x) { + return function recur(t) { + return t.supertypes.every (recur) && t._test (x); + }; + } + var Type$prototype = { 'constructor': {'@@type': 'sanctuary-def/Type@1'}, 'validate': function(x) { - if (!(this._test (x))) return Left ({value: x, propPath: []}); + if (!(_test (x) (this))) 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) { - return Left (this.type === RECORD && this.name !== '' ? - {value: x, - propPath: []} : - {value: result.value.value, + return Left ({value: result.value.value, propPath: Z.concat ([k], result.value.propPath)}); } } @@ -378,6 +390,7 @@ Z.equals (this.type, other.type) && Z.equals (this.name, other.name) && Z.equals (this.url, other.url) && + Z.equals (this.supertypes, other.supertypes) && Z.equals (this.keys, other.keys) && this.keys.every (function(k) { return Z.equals (this.types[k].type, other.types[k].type); @@ -395,6 +408,7 @@ name, // :: String url, // :: String format, // :: (String -> String, String -> String -> String) -> String + supertypes, // :: Array Type test, // :: Any -> Boolean keys, // :: Array String types // :: StrMap { extractor :: a -> Array b, type :: Type } @@ -404,6 +418,7 @@ t.format = format; t.keys = keys; t.name = name; + t.supertypes = supertypes; t.type = type; t.types = types; t.url = url; @@ -422,11 +437,11 @@ // Inconsistent :: Type var Inconsistent = - _Type (INCONSISTENT, '', '', always2 ('???'), K (false), [], {}); + _Type (INCONSISTENT, '', '', always2 ('???'), [], K (false), [], {}); // NoArguments :: Type var NoArguments = - _Type (NO_ARGUMENTS, '', '', always2 ('()'), K (true), [], {}); + _Type (NO_ARGUMENTS, '', '', always2 ('()'), [], K (true), [], {}); // isParameterizedType :: Type -> Boolean function isParameterizedType(t) { @@ -466,11 +481,31 @@ //. type `Type` as a function of type `Any -> Boolean` that tests values //. for membership in the set (though this is an oversimplification). + //# Unknown :: Type + //. + //. Type used to represent missing type information. The type of `[]`, + //. for example, is `Array ???`. + //. + //. May be used with type constructors when defining environments. Given a + //. type constructor `List :: Type -> Type`, one could use `List ($.Unknown)` + //. to include an infinite number of types in an environment: + //. + //. - `List Number` + //. - `List String` + //. - `List (List Number)` + //. - `List (List String)` + //. - `List (List (List Number))` + //. - `List (List (List String))` + //. - `...` + var Unknown = + _Type (UNKNOWN, '', '', always2 ('Unknown'), [], K (true), [], {}); + //# Any :: Type //. //. Type comprising every JavaScript value. var Any = NullaryTypeWithUrl ('Any') + ([]) (K (true)); //# AnyFunction :: Type @@ -478,6 +513,7 @@ //. Type comprising every Function value. var AnyFunction = NullaryTypeWithUrl ('Function') + ([]) (typeofEq ('function')); //# Arguments :: Type @@ -485,6 +521,7 @@ //. Type comprising every [`arguments`][arguments] object. var Arguments = NullaryTypeWithUrl ('Arguments') + ([]) (typeEq ('Arguments')); //# Array :: Type -> Type @@ -492,6 +529,7 @@ //. Constructor for homogeneous Array types. var Array_ = UnaryTypeWithUrl ('Array') + ([]) (typeEq ('Array')) (I); @@ -500,14 +538,16 @@ //. Type whose sole member is `[]`. var Array0 = NullaryTypeWithUrl ('Array0') - (function(x) { return typeEq ('Array') (x) && x.length === 0; }); + ([Array_ (Unknown)]) + (sizeEq (0)); //# Array1 :: Type -> Type //. //. Constructor for singleton Array types. var Array1 = UnaryTypeWithUrl ('Array1') - (function(x) { return typeEq ('Array') (x) && x.length === 1; }) + ([Array_ (Unknown)]) + (sizeEq (1)) (I); //# Array2 :: Type -> Type -> Type @@ -516,7 +556,8 @@ //. a member of `Array2 String Boolean`. var Array2 = BinaryTypeWithUrl ('Array2') - (function(x) { return typeEq ('Array') (x) && x.length === 2; }) + ([Array_ (Unknown)]) + (sizeEq (2)) (function(array2) { return [array2[0]]; }) (function(array2) { return [array2[1]]; }); @@ -525,6 +566,7 @@ //. Type comprising `true` and `false`. var Boolean_ = NullaryTypeWithUrl ('Boolean') + ([]) (typeofEq ('boolean')); //# Date :: Type @@ -532,6 +574,7 @@ //. Type comprising every Date value. var Date_ = NullaryTypeWithUrl ('Date') + ([]) (typeEq ('Date')); //# ValidDate :: Type @@ -539,13 +582,15 @@ //. Type comprising every [`Date`][] value except `new Date (NaN)`. var ValidDate = NullaryTypeWithUrl ('ValidDate') - (function(x) { return Date_._test (x) && !(isNaN (x.valueOf ())); }); + ([Date_]) + (B (complement (isNaN)) (Number)); //# Either :: Type -> Type -> Type //. //. [Either][] type constructor. var Either_ = BinaryTypeWithUrl ('Either') + ([]) (typeEq ('sanctuary-either/Either@1')) (function(either) { return either.isLeft ? [either.value] : []; }) (function(either) { return either.isLeft ? [] : [either.value]; }); @@ -556,6 +601,7 @@ //. constructors such as [`SyntaxError`][] and [`TypeError`][]. var Error_ = NullaryTypeWithUrl ('Error') + ([]) (typeEq ('Error')); // augmentThunk :: NonEmpty (Array Type) -> NonEmpty (Array Type) @@ -589,8 +635,6 @@ last (xs)); } - var test = AnyFunction._test; - var $keys = []; var $types = {}; types.forEach (function(t, idx) { @@ -599,7 +643,14 @@ $types[k] = {extractor: K ([]), type: t}; }); - return _Type (FUNCTION, '', '', format, test, $keys, $types); + return _Type (FUNCTION, + '', + '', + format, + [AnyFunction], + K (true), + $keys, + $types); } //# HtmlElement :: Type @@ -607,6 +658,7 @@ //. Type comprising every [HTML element][]. var HtmlElement = NullaryTypeWithUrl ('HtmlElement') + ([]) (function(x) { return /^\[object HTML.+Element\]$/.test (toString.call (x)); }); @@ -616,6 +668,7 @@ //. [Maybe][] type constructor. var Maybe = UnaryTypeWithUrl ('Maybe') + ([]) (typeEq ('sanctuary-maybe/Maybe@1')) (function(maybe) { return maybe.isJust ? [maybe.value] : []; }); @@ -627,6 +680,7 @@ //. The given type must satisfy the [Monoid][] and [Setoid][] specifications. var NonEmpty = UnaryTypeWithUrl ('NonEmpty') + ([]) (function(x) { return Z.Monoid.test (x) && Z.Setoid.test (x) && @@ -639,6 +693,7 @@ //. Type whose sole member is `null`. var Null = NullaryTypeWithUrl ('Null') + ([]) (typeEq ('Null')); //# Nullable :: Type -> Type @@ -646,6 +701,7 @@ //. Constructor for types that include `null` as a member. var Nullable = UnaryTypeWithUrl ('Nullable') + ([]) (K (true)) (function(nullable) { // eslint-disable-next-line eqeqeq @@ -657,35 +713,45 @@ //. Type comprising every primitive Number value (including `NaN`). var Number_ = NullaryTypeWithUrl ('Number') + ([]) (typeofEq ('number')); + function nonZero(x) { return x !== 0; } + function nonNegative(x) { return x >= 0; } + function positive(x) { return x > 0; } + function negative(x) { return x < 0; } + //# PositiveNumber :: Type //. //. Type comprising every [`Number`][] value greater than zero. var PositiveNumber = NullaryTypeWithUrl ('PositiveNumber') - (function(x) { return Number_._test (x) && x > 0; }); + ([Number_]) + (positive); //# NegativeNumber :: Type //. //. Type comprising every [`Number`][] value less than zero. var NegativeNumber = NullaryTypeWithUrl ('NegativeNumber') - (function(x) { return Number_._test (x) && x < 0; }); + ([Number_]) + (negative); //# ValidNumber :: Type //. //. Type comprising every [`Number`][] value except `NaN`. var ValidNumber = NullaryTypeWithUrl ('ValidNumber') - (function(x) { return Number_._test (x) && !(isNaN (x)); }); + ([Number_]) + (complement (isNaN)); //# NonZeroValidNumber :: Type //. //. Type comprising every [`ValidNumber`][] value except `0` and `-0`. var NonZeroValidNumber = NullaryTypeWithUrl ('NonZeroValidNumber') - (function(x) { return ValidNumber._test (x) && x !== 0; }); + ([ValidNumber]) + (nonZero); //# FiniteNumber :: Type //. @@ -693,28 +759,32 @@ //. `-Infinity`. var FiniteNumber = NullaryTypeWithUrl ('FiniteNumber') - (function(x) { return ValidNumber._test (x) && isFinite (x); }); + ([ValidNumber]) + (isFinite); //# NonZeroFiniteNumber :: Type //. //. Type comprising every [`FiniteNumber`][] value except `0` and `-0`. var NonZeroFiniteNumber = NullaryTypeWithUrl ('NonZeroFiniteNumber') - (function(x) { return FiniteNumber._test (x) && x !== 0; }); + ([FiniteNumber]) + (nonZero); //# PositiveFiniteNumber :: Type //. //. Type comprising every [`FiniteNumber`][] value greater than zero. var PositiveFiniteNumber = NullaryTypeWithUrl ('PositiveFiniteNumber') - (function(x) { return FiniteNumber._test (x) && x > 0; }); + ([FiniteNumber]) + (positive); //# NegativeFiniteNumber :: Type //. //. Type comprising every [`FiniteNumber`][] value less than zero. var NegativeFiniteNumber = NullaryTypeWithUrl ('NegativeFiniteNumber') - (function(x) { return FiniteNumber._test (x) && x < 0; }); + ([FiniteNumber]) + (negative); //# Integer :: Type //. @@ -722,9 +792,9 @@ //. [[`Number.MIN_SAFE_INTEGER`][min] .. [`Number.MAX_SAFE_INTEGER`][max]]. var Integer = NullaryTypeWithUrl ('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; }); @@ -734,7 +804,8 @@ //. Type comprising every [`Integer`][] value except `0` and `-0`. var NonZeroInteger = NullaryTypeWithUrl ('NonZeroInteger') - (function(x) { return Integer._test (x) && x !== 0; }); + ([Integer]) + (nonZero); //# NonNegativeInteger :: Type //. @@ -742,21 +813,24 @@ //. Also known as the set of natural numbers under ISO 80000-2:2009. var NonNegativeInteger = NullaryTypeWithUrl ('NonNegativeInteger') - (function(x) { return Integer._test (x) && x >= 0; }); + ([Integer]) + (nonNegative); //# PositiveInteger :: Type //. //. Type comprising every [`Integer`][] value greater than zero. var PositiveInteger = NullaryTypeWithUrl ('PositiveInteger') - (function(x) { return Integer._test (x) && x > 0; }); + ([Integer]) + (positive); //# NegativeInteger :: Type //. //. Type comprising every [`Integer`][] value less than zero. var NegativeInteger = NullaryTypeWithUrl ('NegativeInteger') - (function(x) { return Integer._test (x) && x < 0; }); + ([Integer]) + (negative); //# Object :: Type //. @@ -769,6 +843,7 @@ //. constructor function. var Object_ = NullaryTypeWithUrl ('Object') + ([]) (typeEq ('Object')); //# Pair :: Type -> Type -> Type @@ -776,6 +851,7 @@ //. [Pair][] type constructor. var Pair = BinaryTypeWithUrl ('Pair') + ([]) (typeEq ('sanctuary-pair/Pair@1')) (function(pair) { return [pair.fst]; }) (function(pair) { return [pair.snd]; }); @@ -785,6 +861,7 @@ //. Type comprising every RegExp value. var RegExp_ = NullaryTypeWithUrl ('RegExp') + ([]) (typeEq ('RegExp')); //# GlobalRegExp :: Type @@ -794,7 +871,8 @@ //. See also [`NonGlobalRegExp`][]. var GlobalRegExp = NullaryTypeWithUrl ('GlobalRegExp') - (function(x) { return RegExp_._test (x) && x.global; }); + ([RegExp_]) + (prop ('global')); //# NonGlobalRegExp :: Type //. @@ -803,7 +881,8 @@ //. See also [`GlobalRegExp`][]. var NonGlobalRegExp = NullaryTypeWithUrl ('NonGlobalRegExp') - (function(x) { return RegExp_._test (x) && !x.global; }); + ([RegExp_]) + (complement (prop ('global'))); //# RegexFlags :: Type //. @@ -829,7 +908,8 @@ //. `{foo: 1, bar: 2, baz: 'XXX'}` is not. var StrMap = UnaryTypeWithUrl ('StrMap') - (Object_._test) + ([Object_]) + (K (true)) (function(strMap) { return Z.reduce (function(xs, x) { xs.push (x); return xs; }, [], @@ -841,6 +921,7 @@ //. Type comprising every primitive String value. var String_ = NullaryTypeWithUrl ('String') + ([]) (typeofEq ('string')); //# Symbol :: Type @@ -848,6 +929,7 @@ //. Type comprising every Symbol value. var Symbol_ = NullaryTypeWithUrl ('Symbol') + ([]) (typeofEq ('symbol')); //# Type :: Type @@ -855,6 +937,7 @@ //. Type comprising every `Type` value. var Type = NullaryTypeWithUrl ('Type') + ([]) (typeEq ('sanctuary-def/Type@1')); //# TypeClass :: Type @@ -862,6 +945,7 @@ //. Type comprising every [`TypeClass`][] value. var TypeClass = NullaryTypeWithUrl ('TypeClass') + ([]) (typeEq ('sanctuary-type-classes/TypeClass@1')); //# Undefined :: Type @@ -869,27 +953,9 @@ //. Type whose sole member is `undefined`. var Undefined = NullaryTypeWithUrl ('Undefined') + ([]) (typeEq ('Undefined')); - //# Unknown :: Type - //. - //. Type used to represent missing type information. The type of `[]`, - //. for example, is `Array ???`. - //. - //. May be used with type constructors when defining environments. Given a - //. type constructor `List :: Type -> Type`, one could use `List ($.Unknown)` - //. to include an infinite number of types in an environment: - //. - //. - `List Number` - //. - `List String` - //. - `List (List Number)` - //. - `List (List String)` - //. - `List (List (List Number))` - //. - `List (List (List String))` - //. - `...` - var Unknown = - _Type (UNKNOWN, '', '', always2 ('Unknown'), K (true), [], {}); - //# env :: Array Type //. //. An array of [types][]: @@ -934,7 +1000,7 @@ ]; // Unchecked :: String -> Type - function Unchecked(s) { return NullaryType (s) ('') (K (true)); } + function Unchecked(s) { return NullaryType (s) ('') ([]) (K (true)); } // production :: Boolean var production = @@ -1298,20 +1364,6 @@ //. //. 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 - //. ('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. function test(env) { return function(t) { return function(x) { @@ -1325,7 +1377,7 @@ //. //. sanctuary-def provides several functions for defining types. - //# NullaryType :: String -> String -> (Any -> Boolean) -> Type + //# NullaryType :: String -> String -> Array Type -> (Any -> Boolean) -> Type //. //. Type constructor for types with no type variables (such as [`Number`][]). //. @@ -1333,10 +1385,13 @@ //. //. - the name of `t` (exposed as `t.name`); //. - //. - the documentation URL of `t` (exposed as `t.url`); and + //. - the documentation URL of `t` (exposed as `t.url`); + //. + //. - an array of supertypes (exposed as `t.supertypes`); and //. - //. - a predicate that accepts any JavaScript value and returns `true` if - //. (and only if) the value is a member of `t`. + //. - a predicate that accepts any value that is a member of every one of + //. the given supertypes, and returns `true` if (and only if) the value + //. is a member of `t`. //. //. For example: //. @@ -1345,6 +1400,7 @@ //. const Integer = $.NullaryType //. ('Integer') //. ('http://example.com/my-package#Integer') + //. ([]) //. (x => typeof x === 'number' && //. Math.floor (x) === x && //. x >= Number.MIN_SAFE_INTEGER && @@ -1354,7 +1410,8 @@ //. const NonZeroInteger = $.NullaryType //. ('NonZeroInteger') //. ('http://example.com/my-package#NonZeroInteger') - //. (x => $.test ([]) (Integer) (x) && x !== 0); + //. ([Integer]) + //. (x => x !== 0); //. //. // rem :: Integer -> NonZeroInteger -> Integer //. const rem = @@ -1397,13 +1454,15 @@ return outer (name); } return function(url) { - return function(test) { - return _Type (NULLARY, name, url, format, test, [], {}); + return function(supertypes) { + return function(test) { + return _Type (NULLARY, name, url, format, supertypes, test, [], {}); + }; }; }; } - //# UnaryType :: String -> String -> (Any -> Boolean) -> (t a -> Array a) -> Type -> Type + //# UnaryType :: String -> String -> Array Type -> (Any -> Boolean) -> (t a -> Array a) -> Type -> Type //. //. Type constructor for types with one type variable (such as [`Array`][]). //. @@ -1413,8 +1472,11 @@ //. //. - the documentation URL of `t` (exposed as `t.url`); //. - //. - a predicate that accepts any JavaScript value and returns `true` - //. if (and only if) the value is a member of `t x` for some type `x`; + //. - an array of supertypes (exposed as `t.supertypes`); + //. + //. - a predicate that accepts any value that is a member of every one of + //. the given supertypes, and returns `true` if (and only if) the value + //. is a member of `t x` for some type `x`; //. //. - a function that takes any value of type `t a` and returns an array //. of the values of type `a` contained in the `t` (exposed as @@ -1435,6 +1497,7 @@ //. const Maybe = $.UnaryType //. ('Maybe') //. ('http://example.com/my-package#Maybe') + //. ([]) //. (x => type (x) === MaybeTypeRep['@@type']) //. (maybe => maybe.isJust ? [maybe.value] : []); //. @@ -1483,28 +1546,40 @@ //. ``` function UnaryType(name) { return function(url) { - return function(test) { - return function(_1) { - return function($1) { - function format(outer, inner) { - return outer ('(' + name + ' ') + - inner ('$1') (show ($1)) + - outer (')'); - } - var types = {$1: {extractor: _1, type: $1}}; - return _Type (UNARY, name, url, format, test, ['$1'], types); + return function(supertypes) { + return function(test) { + return function(_1) { + return function($1) { + function format(outer, inner) { + return outer ('(' + name + ' ') + + inner ('$1') (show ($1)) + + outer (')'); + } + return _Type (UNARY, + name, + url, + format, + supertypes, + test, + ['$1'], + {$1: {extractor: _1, type: $1}}); + }; }; }; }; }; } - // fromUnaryType :: Type -> (Type -> Type) + // 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.supertypes) + (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 -> Array 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 //. [`Array2`][]). @@ -1515,9 +1590,11 @@ //. //. - the documentation URL of `t` (exposed as `t.url`); //. - //. - a predicate that 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`; + //. - an array of supertypes (exposed as `t.supertypes`); + //. + //. - a predicate that accepts any value that is a member of every one of + //. the given supertypes, and returns `true` if (and only if) the value + //. is a member of `t x y` for some types `x` and `y`; //. //. - a function that takes any value of type `t a b` and returns an array //. of the values of type `a` contained in the `t` (exposed as @@ -1543,6 +1620,7 @@ //. const $Pair = $.BinaryType //. ('Pair') //. ('http://example.com/my-package#Pair') + //. ([]) //. (x => type (x) === PairTypeRep['@@type']) //. (({fst}) => [fst]) //. (({snd}) => [snd]); @@ -1563,6 +1641,7 @@ //. const Rank = $.NullaryType //. ('Rank') //. ('http://example.com/my-package#Rank') + //. ([]) //. (x => typeof x === 'string' && //. /^(A|2|3|4|5|6|7|8|9|10|J|Q|K)$/.test (x)); //. @@ -1570,6 +1649,7 @@ //. const Suit = $.NullaryType //. ('Suit') //. ('http://example.com/my-package#Suit') + //. ([]) //. (x => typeof x === 'string' && //. /^[\u2660\u2663\u2665\u2666]$/.test (x)); //. @@ -1601,26 +1681,29 @@ //. ``` function BinaryType(name) { return function(url) { - return function(test) { - return function(_1) { - return function(_2) { - return function($1) { - return function($2) { - function format(outer, inner) { - return outer ('(' + name + ' ') + - inner ('$1') (show ($1)) + - outer (' ') + - inner ('$2') (show ($2)) + - outer (')'); - } - return _Type (BINARY, - name, - url, - format, - test, - ['$1', '$2'], - {$1: {extractor: _1, type: $1}, - $2: {extractor: _2, type: $2}}); + return function(supertypes) { + return function(test) { + return function(_1) { + return function(_2) { + return function($1) { + return function($2) { + function format(outer, inner) { + return outer ('(' + name + ' ') + + inner ('$1') (show ($1)) + + outer (' ') + + inner ('$2') (show ($2)) + + outer (')'); + } + return _Type (BINARY, + name, + url, + format, + supertypes, + test, + ['$1', '$2'], + {$1: {extractor: _1, type: $1}, + $2: {extractor: _2, type: $2}}); + }; }; }; }; @@ -1635,6 +1718,7 @@ function(specialize) { return Z.map (specialize, $2s); }, Z.map (BinaryType (t.name) (t.url) + (t.supertypes) (t._test) (t.types.$1.extractor) (t.types.$2.extractor), @@ -1665,7 +1749,7 @@ //. ``` function EnumType(name) { return function(url) { - return B (NullaryType (name) (url)) (memberOf); + return B (NullaryType (name) (url) ([])) (memberOf); }; } @@ -1677,6 +1761,8 @@ //. //. To define an anonymous record type one must provide: //. + //. - an array of supertypes (exposed as `t.supertypes`); and + //. //. - an object mapping field name to type. //. //. For example: @@ -1751,10 +1837,10 @@ $types[k] = {extractor: function(x) { return [x[k]]; }, type: fields[k]}; }); - return _Type (RECORD, '', '', format, test, keys, $types); + return _Type (RECORD, '', '', format, [], test, keys, $types); } - //# NamedRecordType :: NonEmpty String -> String -> StrMap Type -> Type + //# NamedRecordType :: NonEmpty String -> String -> Array Type -> StrMap Type -> Type //. //. `NamedRecordType` is used to construct named record types. The type //. definition specifies the name and type of each required field. A field is @@ -1764,18 +1850,28 @@ //. //. - the name of `t` (exposed as `t.name`); //. - //. - the documentation URL of `t` (exposed as `t.url`); and + //. - the documentation URL of `t` (exposed as `t.url`); + //. + //. - an array of supertypes (exposed as `t.supertypes`); and //. //. - an object mapping field name to type. //. //. For example: //. //. ```javascript + //. // Circle :: Type + //. const Circle = $.NamedRecordType + //. ('my-package/Circle') + //. ('http://example.com/my-package#Circle') + //. ([]) + //. ({radius: $.PositiveFiniteNumber}); + //. //. // Cylinder :: Type //. const Cylinder = $.NamedRecordType //. ('Cylinder') //. ('http://example.com/my-package#Cylinder') - //. ({radius: $.PositiveFiniteNumber, height: $.PositiveFiniteNumber}); + //. ([Circle]) + //. ({height: $.PositiveFiniteNumber}); //. //. // volume :: Cylinder -> PositiveFiniteNumber //. const volume = @@ -1802,28 +1898,39 @@ //. ``` function NamedRecordType(name) { return function(url) { - return function(fields) { - var keys = sortedKeys (fields); + return function(supertypes) { + return function(fields) { + var keys = sortedKeys (fields); - function format(outer, inner) { - return outer (name); - } - - function test(x) { - if (x == null) return false; - var missing = {}; - keys.forEach (function(k) { missing[k] = k; }); - for (var k in x) delete missing[k]; - return isEmpty (Object.keys (missing)); - } + function format(outer, inner) { + return outer (name); + } - var $types = {}; - keys.forEach (function(k) { - $types[k] = {extractor: function(x) { return [x[k]]; }, - type: fields[k]}; - }); + function test(x) { + if (x == null) return false; + var missing = {}; + keys.forEach (function(k) { missing[k] = k; }); + for (var k in x) delete missing[k]; + return isEmpty (Object.keys (missing)) && keys.every (function(k) { + return _test (x[k]) (fields[k]); + }); + } - return _Type (RECORD, name, url, format, test, keys, $types); + var $types = {}; + keys.forEach (function(k) { + $types[k] = {extractor: function(x) { return [x[k]]; }, + type: fields[k]}; + }); + + return _Type (RECORD, + name, + url, + format, + supertypes, + test, + keys, + $types); + }; }; }; } @@ -1885,7 +1992,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 _Type (VARIABLE, name, '', always2 (name), K (true), [], {}); + return _Type (VARIABLE, name, '', always2 (name), [], K (true), [], {}); } //# UnaryTypeVariable :: String -> Type -> Type @@ -1942,7 +2049,7 @@ outer (')'); } var types = {$1: {extractor: K ([]), type: $1}}; - return _Type (VARIABLE, name, '', format, K (true), ['$1'], types); + return _Type (VARIABLE, name, '', format, [], K (true), ['$1'], types); }; } @@ -1973,7 +2080,7 @@ var keys = ['$1', '$2']; var types = {$1: {extractor: K ([]), type: $1}, $2: {extractor: K ([]), type: $2}}; - return _Type (VARIABLE, name, '', format, K (true), keys, types); + return _Type (VARIABLE, name, '', format, [], K (true), keys, types); }; }; } @@ -2616,22 +2723,28 @@ // fromUncheckedUnaryType :: (Type -> Type) -> Type -> Type function fromUncheckedUnaryType(typeConstructor) { var t = typeConstructor (Unknown); - var _1 = t.types.$1.extractor; return def (t.name) ({}) ([Type, Type]) - (UnaryType (t.name) (t.url) (t._test) (_1)); + (UnaryType (t.name) + (t.url) + (t.supertypes) + (t._test) + (t.types.$1.extractor)); } // fromUncheckedBinaryType :: (Type -> Type -> Type) -> Type -> Type -> Type function fromUncheckedBinaryType(typeConstructor) { var t = typeConstructor (Unknown) (Unknown); - var _1 = t.types.$1.extractor; - var _2 = t.types.$2.extractor; return def (t.name) ({}) ([Type, Type, Type]) - (BinaryType (t.name) (t.url) (t._test) (_1) (_2)); + (BinaryType (t.name) + (t.url) + (t.supertypes) + (t._test) + (t.types.$1.extractor) + (t.types.$2.extractor)); } return { @@ -2699,18 +2812,23 @@ NullaryType: def ('NullaryType') ({}) - ([String_, String_, Function_ ([Any, Boolean_]), Type]) + ([String_, + String_, + Array_ (Type), + Function_ ([Any, Boolean_]), + Type]) (NullaryType), UnaryType: def ('UnaryType') ({}) ([String_, String_, + Array_ (Type), Unchecked ('(Any -> Boolean)'), Unchecked ('(t a -> Array a)'), Unchecked ('Type -> Type')]) (function(name) { - return B (B (B (def (name) ({}) ([Type, Type])))) + return B (B (B (B (def (name) ({}) ([Type, Type]))))) (UnaryType (name)); }), BinaryType: @@ -2718,12 +2836,13 @@ ({}) ([String_, String_, + Array_ (Type), Unchecked ('(Any -> Boolean)'), Unchecked ('(t a b -> Array a)'), Unchecked ('(t a b -> Array b)'), Unchecked ('Type -> Type -> Type')]) (function(name) { - return B (B (B (B (def (name) ({}) ([Type, Type, Type]))))) + return B (B (B (B (B (def (name) ({}) ([Type, Type, Type])))))) (BinaryType (name)); }), EnumType: @@ -2739,7 +2858,7 @@ NamedRecordType: def ('NamedRecordType') ({}) - ([NonEmpty (String_), String_, StrMap (Type), Type]) + ([NonEmpty (String_), String_, Array_ (Type), StrMap (Type), Type]) (NamedRecordType), TypeVariable: def ('TypeVariable') @@ -2811,7 +2930,6 @@ //. [enumerated types]: https://en.wikipedia.org/wiki/Enumerated_type //. [max]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER //. [min]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_SAFE_INTEGER -//. [record type]: #RecordType //. [semigroup]: https://en.wikipedia.org/wiki/Semigroup //. [type class]: #type-classes //. [type variables]: #TypeVariable diff --git a/test/NODE_ENV.js b/test/NODE_ENV.js index 96d364c..7770c40 100644 --- a/test/NODE_ENV.js +++ b/test/NODE_ENV.js @@ -14,7 +14,7 @@ suite ('NODE_ENV', () => { const invalid = new TypeError (`Invalid value -NullaryType :: String -> String -> (Any -> Boolean) -> Type +NullaryType :: String -> String -> Array Type -> (Any -> Boolean) -> Type ^^^^^^ 1 diff --git a/test/index.js b/test/index.js index 702d28b..df04ca9 100644 --- a/test/index.js +++ b/test/index.js @@ -18,6 +18,9 @@ const eq = require ('./internal/eq'); const throws = require ('./internal/throws'); +// complement :: (a -> Boolean) -> a -> Boolean +const complement = pred => x => !(pred (x)); + // curry2 :: ((a, b) -> c) -> a -> b -> c const curry2 = f => x => y => f (x, y); @@ -1219,11 +1222,11 @@ The value at position 1 is not a member of ‘{ length :: a }’. test ('supports named record types', () => { eq (typeof $.NamedRecordType) ('function'); eq ($.NamedRecordType.length) (1); - eq (show ($.NamedRecordType)) ('NamedRecordType :: NonEmpty String -> String -> StrMap Type -> Type'); - eq (show ($.NamedRecordType ('Circle') ('') ({radius: $.PositiveFiniteNumber}))) ('Circle'); + eq (show ($.NamedRecordType)) ('NamedRecordType :: NonEmpty String -> String -> Array Type -> StrMap Type -> Type'); + eq (show ($.NamedRecordType ('Circle') ('') ([]) ({radius: $.PositiveFiniteNumber}))) ('Circle'); // Empty :: Type - const Empty = $.NamedRecordType ('Empty') ('') ({}); + const Empty = $.NamedRecordType ('Empty') ('') ([]) ({}); eq ($.test ([]) (Empty) (null)) (false); eq ($.test ([]) (Empty) (undefined)) (false); @@ -1236,12 +1239,12 @@ The value at position 1 is not a member of ‘{ length :: a }’. eq ($.test ([]) (Empty) ([])) (true); eq ($.test ([]) (Empty) ({})) (true); - throws (() => { $.NamedRecordType ('Circle') ('') ({radius: Number}); }) + throws (() => { $.NamedRecordType ('Circle') ('') ([]) ({radius: Number}); }) (new TypeError (`Invalid value -NamedRecordType :: NonEmpty String -> String -> StrMap Type -> Type - ^^^^ - 1 +NamedRecordType :: NonEmpty String -> String -> Array Type -> StrMap Type -> Type + ^^^^ + 1 1) ${Number} :: Function @@ -1254,8 +1257,16 @@ See https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#Type for info const Circle = $.NamedRecordType ('Circle') ('http://example.com/my-package#Circle') + ([]) ({radius: $.PositiveFiniteNumber}); + // Cylinder :: Type + const Cylinder = $.NamedRecordType + ('Cylinder') + ('http://example.com/my-package#Cylinder') + ([Circle]) + ({height: $.PositiveFiniteNumber}); + const isCircle = $.test ([]) (Circle); eq (isCircle (null)) (false); eq (isCircle ({})) (false); @@ -1264,6 +1275,20 @@ See https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#Type for info eq (isCircle ({radius: 1})) (true); eq (isCircle ({radius: 1, height: 1})) (true); + const isCylinder = $.test ([]) (Cylinder); + eq (isCylinder (null)) (false); + eq (isCylinder ({})) (false); + eq (isCylinder ({radius: null})) (false); + eq (isCylinder ({height: null})) (false); + eq (isCylinder ({radius: 0})) (false); + eq (isCylinder ({height: 0})) (false); + eq (isCylinder ({radius: 1})) (false); + eq (isCylinder ({height: 1})) (false); + eq (isCylinder ({radius: 0, height: 0})) (false); + eq (isCylinder ({radius: 0, height: 1})) (false); + eq (isCylinder ({radius: 1, height: 0})) (false); + eq (isCylinder ({radius: 1, height: 1})) (true); + // area :: Circle -> PositiveFiniteNumber const area = def ('area') @@ -1384,11 +1409,13 @@ Since there is no type of which all the above values are members, the type-varia test ('provides the "Any" type', () => { eq ($.Any.name) ('Any'); eq ($.Any.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#Any`); + eq ($.Any.supertypes) ([]); }); test ('provides the "AnyFunction" type', () => { eq ($.AnyFunction.name) ('Function'); eq ($.AnyFunction.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#Function`); + eq ($.AnyFunction.supertypes) ([]); function Identity(x) { this.value = x; } Identity['@@type'] = 'my-package/Identity'; @@ -1403,16 +1430,19 @@ Since there is no type of which all the above values are members, the type-varia test ('provides the "Arguments" type', () => { eq ($.Arguments.name) ('Arguments'); eq ($.Arguments.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#Arguments`); + eq ($.Arguments.supertypes) ([]); }); test ('provides the "Array" type constructor', () => { eq (($.Array (a)).name) ('Array'); eq (($.Array (a)).url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#Array`); + eq (($.Array (a)).supertypes) ([]); }); test ('provides the "Array0" type', () => { eq ($.Array0.name) ('Array0'); eq ($.Array0.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#Array0`); + eq ($.Array0.supertypes) ([$.Array ($.Unknown)]); const isEmptyArray = $.test ([]) ($.Array0); eq (isEmptyArray (null)) (false); @@ -1427,6 +1457,7 @@ Since there is no type of which all the above values are members, the type-varia eq (show ($.Array1 (a))) ('(Array1 a)'); eq (($.Array1 (a)).name) ('Array1'); eq (($.Array1 (a)).url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#Array1`); + eq (($.Array1 (a)).supertypes) ([$.Array ($.Unknown)]); const isSingletonStringArray = $.test ([]) ($.Array1 ($.String)); eq (isSingletonStringArray (null)) (false); @@ -1443,6 +1474,7 @@ Since there is no type of which all the above values are members, the type-varia eq (show ($.Array2 (a) (b))) ('(Array2 a b)'); eq (($.Array2 (a) (b)).name) ('Array2'); eq (($.Array2 (a) (b)).url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#Array2`); + eq (($.Array2 (a) (b)).supertypes) ([$.Array ($.Unknown)]); // fst :: Array2 a b -> a const fst = def ('fst') ({}) ([$.Array2 (a) (b), a]) (array2 => array2[0]); @@ -1471,11 +1503,13 @@ See https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#Array2 for in test ('provides the "Boolean" type', () => { eq ($.Boolean.name) ('Boolean'); eq ($.Boolean.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#Boolean`); + eq ($.Boolean.supertypes) ([]); }); test ('provides the "Date" type', () => { eq ($.Date.name) ('Date'); eq ($.Date.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#Date`); + eq ($.Date.supertypes) ([]); }); test ('provides the "Either" type constructor', () => { @@ -1497,16 +1531,19 @@ See https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#Array2 for in test ('provides the "Error" type', () => { eq ($.Error.name) ('Error'); eq ($.Error.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#Error`); + eq ($.Error.supertypes) ([]); }); test ('provides the "Function" type constructor', () => { eq (($.Function ([a, a])).name) (''); eq (($.Function ([a, a])).url) (''); + eq (($.Function ([a, a])).supertypes) ([$.AnyFunction]); }); test ('provides the "HtmlElement" type', () => { eq ($.HtmlElement.name) ('HtmlElement'); eq ($.HtmlElement.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#HtmlElement`); + eq ($.HtmlElement.supertypes) ([]); }); test ('provides the "Maybe" type constructor', () => { @@ -1527,6 +1564,7 @@ See https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#Array2 for in test ('provides the "NonEmpty" type constructor', () => { eq (($.NonEmpty ($.String)).name) ('NonEmpty'); eq (($.NonEmpty ($.String)).url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#NonEmpty`); + eq (($.NonEmpty ($.String)).supertypes) ([]); const isNonEmptyIntegerArray = $.test ([]) ($.NonEmpty ($.Array ($.Integer))); eq (isNonEmptyIntegerArray ([])) (false); @@ -1537,21 +1575,25 @@ See https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#Array2 for in test ('provides the "Null" type', () => { eq ($.Null.name) ('Null'); eq ($.Null.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#Null`); + eq ($.Null.supertypes) ([]); }); test ('provides the "Nullable" type constructor', () => { eq (($.Nullable (a)).name) ('Nullable'); eq (($.Nullable (a)).url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#Nullable`); + eq (($.Nullable (a)).supertypes) ([]); }); test ('provides the "Number" type', () => { eq ($.Number.name) ('Number'); eq ($.Number.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#Number`); + eq ($.Number.supertypes) ([]); }); test ('provides the "Object" type', () => { eq ($.Object.name) ('Object'); eq ($.Object.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#Object`); + eq ($.Object.supertypes) ([]); }); test ('provides the "Pair" type constructor', () => { @@ -1573,41 +1615,49 @@ See https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#Array2 for in test ('provides the "RegExp" type', () => { eq ($.RegExp.name) ('RegExp'); eq ($.RegExp.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#RegExp`); + eq ($.RegExp.supertypes) ([]); }); test ('provides the "String" type', () => { eq ($.String.name) ('String'); eq ($.String.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#String`); + eq ($.String.supertypes) ([]); }); test ('provides the "Symbol" type', () => { eq ($.Symbol.name) ('Symbol'); eq ($.Symbol.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#Symbol`); + eq ($.Symbol.supertypes) ([]); }); test ('provides the "Type" type', () => { eq ($.Type.name) ('Type'); eq ($.Type.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#Type`); + eq ($.Type.supertypes) ([]); }); test ('provides the "TypeClass" type', () => { eq ($.TypeClass.name) ('TypeClass'); eq ($.TypeClass.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#TypeClass`); + eq ($.TypeClass.supertypes) ([]); }); test ('provides the "Undefined" type', () => { eq ($.Undefined.name) ('Undefined'); eq ($.Undefined.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#Undefined`); + eq ($.Undefined.supertypes) ([]); }); test ('provides the "Unknown" type', () => { eq ($.Unknown.name) (''); eq ($.Unknown.url) (''); + eq ($.Unknown.supertypes) ([]); }); test ('provides the "ValidDate" type', () => { eq ($.ValidDate.name) ('ValidDate'); eq ($.ValidDate.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#ValidDate`); + eq ($.ValidDate.supertypes) ([$.Date]); // sinceEpoch :: ValidDate -> Number const sinceEpoch = @@ -1636,6 +1686,7 @@ See https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#ValidDate for test ('provides the "PositiveNumber" type', () => { eq ($.PositiveNumber.name) ('PositiveNumber'); eq ($.PositiveNumber.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#PositiveNumber`); + eq ($.PositiveNumber.supertypes) ([$.Number]); const isPositiveNumber = $.test ([]) ($.PositiveNumber); eq (isPositiveNumber (null)) (false); @@ -1651,6 +1702,7 @@ See https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#ValidDate for test ('provides the "NegativeNumber" type', () => { eq ($.NegativeNumber.name) ('NegativeNumber'); eq ($.NegativeNumber.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#NegativeNumber`); + eq ($.NegativeNumber.supertypes) ([$.Number]); const isNegativeNumber = $.test ([]) ($.NegativeNumber); eq (isNegativeNumber (null)) (false); @@ -1666,6 +1718,7 @@ See https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#ValidDate for test ('provides the "ValidNumber" type', () => { eq ($.ValidNumber.name) ('ValidNumber'); eq ($.ValidNumber.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#ValidNumber`); + eq ($.ValidNumber.supertypes) ([$.Number]); const isValidNumber = $.test ([]) ($.ValidNumber); eq (isValidNumber (NaN)) (false); @@ -1676,6 +1729,7 @@ See https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#ValidDate for test ('provides the "NonZeroValidNumber" type', () => { eq ($.NonZeroValidNumber.name) ('NonZeroValidNumber'); eq ($.NonZeroValidNumber.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#NonZeroValidNumber`); + eq ($.NonZeroValidNumber.supertypes) ([$.ValidNumber]); const isNonZeroValidNumber = $.test ([]) ($.NonZeroValidNumber); eq (isNonZeroValidNumber (0)) (false); @@ -1687,6 +1741,7 @@ See https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#ValidDate for test ('provides the "FiniteNumber" type', () => { eq ($.FiniteNumber.name) ('FiniteNumber'); eq ($.FiniteNumber.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#FiniteNumber`); + eq ($.FiniteNumber.supertypes) ([$.ValidNumber]); const isFiniteNumber = $.test ([]) ($.FiniteNumber); eq (isFiniteNumber (Infinity)) (false); @@ -1698,6 +1753,7 @@ See https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#ValidDate for test ('provides the "PositiveFiniteNumber" type', () => { eq ($.PositiveFiniteNumber.name) ('PositiveFiniteNumber'); eq ($.PositiveFiniteNumber.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#PositiveFiniteNumber`); + eq ($.PositiveFiniteNumber.supertypes) ([$.FiniteNumber]); const isPositiveFiniteNumber = $.test ([]) ($.PositiveFiniteNumber); eq (isPositiveFiniteNumber (null)) (false); @@ -1713,6 +1769,7 @@ See https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#ValidDate for test ('provides the "NegativeFiniteNumber" type', () => { eq ($.NegativeFiniteNumber.name) ('NegativeFiniteNumber'); eq ($.NegativeFiniteNumber.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#NegativeFiniteNumber`); + eq ($.NegativeFiniteNumber.supertypes) ([$.FiniteNumber]); const isNegativeFiniteNumber = $.test ([]) ($.NegativeFiniteNumber); eq (isNegativeFiniteNumber (null)) (false); @@ -1728,6 +1785,7 @@ See https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#ValidDate for test ('provides the "NonZeroFiniteNumber" type', () => { eq ($.NonZeroFiniteNumber.name) ('NonZeroFiniteNumber'); eq ($.NonZeroFiniteNumber.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#NonZeroFiniteNumber`); + eq ($.NonZeroFiniteNumber.supertypes) ([$.FiniteNumber]); const isNonZeroFiniteNumber = $.test ([]) ($.NonZeroFiniteNumber); eq (isNonZeroFiniteNumber (0)) (false); @@ -1741,6 +1799,7 @@ See https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#ValidDate for test ('provides the "Integer" type', () => { eq ($.Integer.name) ('Integer'); eq ($.Integer.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#Integer`); + eq ($.Integer.supertypes) ([$.ValidNumber]); const isInteger = $.test ([]) ($.Integer); eq (isInteger (3.14)) (false); @@ -1753,6 +1812,7 @@ See https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#ValidDate for test ('provides the "NonZeroInteger" type', () => { eq ($.NonZeroInteger.name) ('NonZeroInteger'); eq ($.NonZeroInteger.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#NonZeroInteger`); + eq ($.NonZeroInteger.supertypes) ([$.Integer]); const isNonZeroInteger = $.test ([]) ($.NonZeroInteger); eq (isNonZeroInteger (0)) (false); @@ -1765,6 +1825,7 @@ See https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#ValidDate for test ('provides the "NonNegativeInteger" type', () => { eq ($.NonNegativeInteger.name) ('NonNegativeInteger'); eq ($.NonNegativeInteger.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#NonNegativeInteger`); + eq ($.NonNegativeInteger.supertypes) ([$.Integer]); const isNonNegativeInteger = $.test ([]) ($.NonNegativeInteger); eq (isNonNegativeInteger (0)) (true); @@ -1778,6 +1839,7 @@ See https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#ValidDate for test ('provides the "PositiveInteger" type', () => { eq ($.PositiveInteger.name) ('PositiveInteger'); eq ($.PositiveInteger.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#PositiveInteger`); + eq ($.PositiveInteger.supertypes) ([$.Integer]); const isPositiveInteger = $.test ([]) ($.PositiveInteger); eq (isPositiveInteger (1.5)) (false); @@ -1789,6 +1851,7 @@ See https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#ValidDate for test ('provides the "NegativeInteger" type', () => { eq ($.NegativeInteger.name) ('NegativeInteger'); eq ($.NegativeInteger.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#NegativeInteger`); + eq ($.NegativeInteger.supertypes) ([$.Integer]); const isNegativeInteger = $.test ([]) ($.NegativeInteger); eq (isNegativeInteger (-1.5)) (false); @@ -1800,6 +1863,7 @@ See https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#ValidDate for test ('provides the "GlobalRegExp" type', () => { eq ($.GlobalRegExp.name) ('GlobalRegExp'); eq ($.GlobalRegExp.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#GlobalRegExp`); + eq ($.GlobalRegExp.supertypes) ([$.RegExp]); const isGlobalRegExp = $.test ([]) ($.GlobalRegExp); eq (isGlobalRegExp (null)) (false); @@ -1817,6 +1881,7 @@ See https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#ValidDate for test ('provides the "NonGlobalRegExp" type', () => { eq ($.NonGlobalRegExp.name) ('NonGlobalRegExp'); eq ($.NonGlobalRegExp.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#NonGlobalRegExp`); + eq ($.NonGlobalRegExp.supertypes) ([$.RegExp]); const isNonGlobalRegExp = $.test ([]) ($.NonGlobalRegExp); eq (isNonGlobalRegExp (null)) (false); @@ -1834,6 +1899,7 @@ See https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#ValidDate for test ('provides the "RegexFlags" type', () => { eq ($.RegexFlags.name) ('RegexFlags'); eq ($.RegexFlags.url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#RegexFlags`); + eq ($.RegexFlags.supertypes) ([]); const isRegexFlags = $.test ([]) ($.RegexFlags); eq (isRegexFlags ('')) (true); @@ -1859,6 +1925,7 @@ See https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#ValidDate for eq (show ($.StrMap (a))) ('(StrMap a)'); eq (($.StrMap (a)).name) ('StrMap'); eq (($.StrMap (a)).url) (`https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#StrMap`); + eq (($.StrMap (a)).supertypes) ([$.Object]); // id :: a -> a const id = @@ -3031,6 +3098,7 @@ Since there is no type of which all the above values are members, the type-varia const Void = $.NullaryType ('Void') ('http://example.com/my-package#Void') + ([]) (x => { count += 1; return false; }); const env = [$.Array ($.Unknown), $.Maybe ($.Unknown), $.Number, Void]; @@ -3122,7 +3190,40 @@ suite ('NullaryType', () => { test ('is a ternary function', () => { eq (typeof $.NullaryType) ('function'); eq ($.NullaryType.length) (1); - eq (show ($.NullaryType)) ('NullaryType :: String -> String -> (Any -> Boolean) -> Type'); + eq (show ($.NullaryType)) ('NullaryType :: String -> String -> Array Type -> (Any -> Boolean) -> Type'); + }); + + test ('supports subtyping', () => { + // Number_ :: Type + const Number_ = $.NullaryType + ('Number') + ('') + ([]) + (x => typeof x === 'number'); + + // ValidNumber :: Type + const ValidNumber = $.NullaryType + ('ValidNumber') + ('') + ([Number_]) + (complement (isNaN)); + + // FiniteNumber :: Type + const FiniteNumber = $.NullaryType + ('FiniteNumber') + ('') + ([ValidNumber]) + (isFinite); + + eq ($.test ([]) (Number_) (null)) (false); + eq ($.test ([]) (Number_) (NaN)) (true); + eq ($.test ([]) (ValidNumber) (null)) (false); + eq ($.test ([]) (ValidNumber) (NaN)) (false); + eq ($.test ([]) (ValidNumber) (Infinity)) (true); + eq ($.test ([]) (FiniteNumber) (null)) (false); + eq ($.test ([]) (FiniteNumber) (NaN)) (false); + eq ($.test ([]) (FiniteNumber) (Infinity)) (false); + eq ($.test ([]) (FiniteNumber) (0)) (true); }); }); @@ -3132,7 +3233,7 @@ suite ('UnaryType', () => { test ('is a quaternary function', () => { eq (typeof $.UnaryType) ('function'); eq ($.UnaryType.length) (1); - eq (show ($.UnaryType)) ('UnaryType :: String -> String -> (Any -> Boolean) -> (t a -> Array a) -> Type -> Type'); + eq (show ($.UnaryType)) ('UnaryType :: String -> String -> Array Type -> (Any -> Boolean) -> (t a -> Array a) -> Type -> Type'); }); test ('returns a type constructor which type checks its arguments', () => { @@ -3140,7 +3241,8 @@ suite ('UnaryType', () => { const MyUnaryType = $.UnaryType ('MyUnaryType') ('') - (x => type (x) === 'MyUnaryType@1') + ([]) + (x => type (x) === 'my-package/MyUnaryType@1') (_ => []); throws (() => { MyUnaryType ({x: $.Number, y: $.Number}); }) @@ -3158,6 +3260,23 @@ See https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#Type for info `)); }); + test ('supports subtyping', () => { + // Palindrome :: Type -> Type + const Palindrome = $.UnaryType + ('Palindrome') + ('http://example.com/my-package#Palindrome') + ([$.Array ($.Unknown)]) + (xs => Z.equals (xs, Z.reverse (xs))) + (palindrome => palindrome); + + eq ($.test ([]) (Palindrome ($.Number)) (null)) (false); + eq ($.test ([]) (Palindrome ($.Number)) ([])) (true); + eq ($.test ([]) (Palindrome ($.Number)) ([1])) (true); + eq ($.test ([]) (Palindrome ($.Number)) ([1, 2, 3])) (false); + eq ($.test ([]) (Palindrome ($.Number)) ([1, 2, 1])) (true); + eq ($.test ([]) (Palindrome ($.Number)) (['foo', 'bar', 'foo'])) (false); + }); + }); suite ('BinaryType', () => { @@ -3165,7 +3284,7 @@ suite ('BinaryType', () => { test ('is a quinary function', () => { eq (typeof $.BinaryType) ('function'); eq ($.BinaryType.length) (1); - eq (show ($.BinaryType)) ('BinaryType :: String -> String -> (Any -> Boolean) -> (t a b -> Array a) -> (t a b -> Array b) -> Type -> Type -> Type'); + eq (show ($.BinaryType)) ('BinaryType :: String -> String -> Array Type -> (Any -> Boolean) -> (t a b -> Array a) -> (t a b -> Array b) -> Type -> Type -> Type'); }); test ('returns a type constructor which type checks its arguments', () => { @@ -3173,6 +3292,7 @@ suite ('BinaryType', () => { const MyBinaryType = $.BinaryType ('MyBinaryType') ('') + ([]) (x => type (x) === 'my-package/MyBinaryType@1') (_ => []) (_ => []); @@ -3192,6 +3312,23 @@ See https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#Type for info `)); }); + test ('supports subtyping', () => { + // Different :: Type -> Type -> Type + const Different = $.BinaryType + ('Different') + ('http://example.com/my-package#Different') + ([$.Array2 ($.Unknown) ($.Unknown)]) + (array2 => !(Z.equals (array2[0], array2[1]))) + (different => [different[0]]) + (different => [different[1]]); + + eq ($.test ([]) (Different ($.String) ($.String)) ([null, null])) (false); + eq ($.test ([]) (Different ($.String) ($.String)) ([null, 'foo'])) (false); + eq ($.test ([]) (Different ($.String) ($.String)) (['foo', null])) (false); + eq ($.test ([]) (Different ($.String) ($.String)) (['foo', 'foo'])) (false); + eq ($.test ([]) (Different ($.String) ($.String)) (['foo', 'bar'])) (true); + }); + }); suite ('TypeVariable', () => { @@ -3356,14 +3493,26 @@ suite ('interoperability', () => { eq (Z.equals ($.RecordType ({x: $.Number}), $.RecordType ({x: $.Number}))) (true); eq (Z.equals ($.RecordType ({x: $.Number}), $.RecordType ({y: $.Number}))) (false); eq (Z.equals ($.RecordType ({x: $.Number}), $.RecordType ({x: $.String}))) (false); - eq (Z.equals ($.NullaryType ('X') ('') (x => true), $.NullaryType ('X') ('') (x => true))) (true); - eq (Z.equals ($.NullaryType ('X') ('') (x => true), $.NullaryType ('Y') ('') (x => true))) (false); - eq (Z.equals ($.NullaryType ('X') ('') (x => true), $.NullaryType ('X') ('') (x => false))) (true); - eq (Z.equals ($.Array ($.NullaryType ('X') ('http://x.com/') (x => true)), - $.Array ($.NullaryType ('X') ('http://x.com/') (x => true)))) + eq (Z.equals ($.NullaryType ('X') ('') ([$.Number]) (x => true), + $.NullaryType ('X') ('') ([$.Number]) (x => true))) + (true); + eq (Z.equals ($.NullaryType ('X') ('') ([$.Number]) (x => true), + $.NullaryType ('X') ('') ([$.String]) (x => true))) + (false); + eq (Z.equals ($.NullaryType ('X') ('') ([]) (x => true), + $.NullaryType ('X') ('') ([]) (x => true))) + (true); + eq (Z.equals ($.NullaryType ('X') ('') ([]) (x => true), + $.NullaryType ('Y') ('') ([]) (x => true))) + (false); + eq (Z.equals ($.NullaryType ('X') ('') ([]) (x => true), + $.NullaryType ('X') ('') ([]) (x => false))) + (true); + eq (Z.equals ($.Array ($.NullaryType ('X') ('http://x.com/') ([]) (x => true)), + $.Array ($.NullaryType ('X') ('http://x.com/') ([]) (x => true)))) (true); - eq (Z.equals ($.Array ($.NullaryType ('X') ('http://x.com/') (x => true)), - $.Array ($.NullaryType ('X') ('http://x.org/') (x => true)))) + eq (Z.equals ($.Array ($.NullaryType ('X') ('http://x.com/') ([]) (x => true)), + $.Array ($.NullaryType ('X') ('http://x.org/') ([]) (x => true)))) (false); });