Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/sanctuary-js/sanctuary-def
Run-time type system for JavaScript
https://github.com/sanctuary-js/sanctuary-def
fantasy-land sanctuary
Last synced: 6 days ago
JSON representation
Run-time type system for JavaScript
- Host: GitHub
- URL: https://github.com/sanctuary-js/sanctuary-def
- Owner: sanctuary-js
- License: mit
- Created: 2015-10-07T05:29:16.000Z (about 9 years ago)
- Default Branch: main
- Last Pushed: 2024-11-10T15:41:37.000Z (about 2 months ago)
- Last Synced: 2024-12-06T22:06:31.641Z (27 days ago)
- Topics: fantasy-land, sanctuary
- Language: JavaScript
- Homepage:
- Size: 1.47 MB
- Stars: 293
- Watchers: 16
- Forks: 24
- Open Issues: 21
-
Metadata Files:
- Readme: README.md
- Contributing: .github/CONTRIBUTING.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
README
# sanctuary-def
sanctuary-def is a run-time type system for JavaScript. It facilitates
the definition of curried JavaScript functions that are explicit about
the number of arguments to which they may be applied and the types of
those arguments.It is conventional to import the package as `$`:
```javascript
const $ = require ('sanctuary-def');
```The next step is to define an environment. An environment is an array
of [types][]. [`env`][] is an environment containing all the built-in
JavaScript types. It may be used as the basis for environments that
include custom types in addition to the built-in types:```javascript
// Integer :: Type
const Integer = '...';// NonZeroInteger :: Type
const NonZeroInteger = '...';// env :: Array Type
const env = $.env.concat ([Integer, NonZeroInteger]);
```Type constructors such as `List :: Type -> Type` cannot be included in
an environment as they're not of the correct type. One could, though,
use a type constructor to define a fixed number of concrete types:```javascript
// env :: Array Type
const env = $.env.concat ([
List ($.Number), // :: Type
List ($.String), // :: Type
List (List ($.Number)), // :: Type
List (List ($.String)), // :: Type
List (List (List ($.Number))), // :: Type
List (List (List ($.String))), // :: Type
]);
```Not only would this be tedious, but one could never enumerate all possible
types as there are infinitely many. Instead, one should use [`Unknown`][]:```javascript
// env :: Array Type
const env = $.env.concat ([List ($.Unknown)]);
```The next step is to define a `def` function for the environment using
`$.create`:```javascript
// def :: String -> StrMap (Array TypeClass) -> Array Type -> Function -> Function
const def = $.create ({checkTypes: true, env});
```The `checkTypes` option determines whether type checking is enabled.
This allows one to only pay the performance cost of run-time type checking
during development. For example:```javascript
// def :: String -> StrMap (Array TypeClass) -> Array Type -> Function -> Function
const def = $.create ({
checkTypes: process.env.NODE_ENV === 'development',
env,
});
````def` is a function for defining functions. For example:
```javascript
// add :: Number -> Number -> Number
const add =
def ('add') // name
({}) // type-class constraints
([$.Number, $.Number, $.Number]) // input and output types
(x => y => x + y); // implementation
````[$.Number, $.Number, $.Number]` specifies that `add` takes two arguments
of type `Number`, one at a time, and returns a value of type `Number`.Applying `add` to two arguments, one at a time, gives the expected result:
```javascript
add (2) (2);
// => 4
```Applying `add` to multiple arguments at once results in an exception being
thrown:```javascript
add (2, 2, 2);
// ! TypeError: ‘add’ applied to the wrong number of arguments
//
// add :: Number -> Number -> Number
// ^^^^^^
// 1
//
// Expected one argument but received three arguments:
//
// - 2
// - 2
// - 2
```Applying `add` to one argument produces a function awaiting the remaining
argument. This is known as partial application. Partial application allows
more specific functions to be defined in terms of more general ones:```javascript
// inc :: Number -> Number
const inc = add (1);inc (7);
// => 8
```JavaScript's implicit type coercion often obfuscates the source of type
errors. Consider the following function:```javascript
// _add :: Number -> Number -> Number
const _add = x => y => x + y;
```The type signature indicates that `_add` takes arguments of type `Number`,
but this is not enforced. This allows type errors to be silently ignored:```javascript
_add ('2') ('2');
// => '22'
````add`, on the other hand, throws if applied to arguments of the wrong
types:```javascript
add ('2') ('2');
// ! TypeError: Invalid value
//
// add :: Number -> Number -> Number
// ^^^^^^
// 1
//
// 1) "2" :: String
//
// The value at position 1 is not a member of ‘Number’.
```Type checking is performed as arguments are provided (rather than once all
arguments have been provided), so type errors are reported early:```javascript
add ('X');
// ! TypeError: Invalid value
//
// add :: Number -> Number -> Number
// ^^^^^^
// 1
//
// 1) "X" :: String
//
// The value at position 1 is not a member of ‘Number’.
```### Types
Conceptually, a type is a set of values. One can think of a value of
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))`
- `...`#### `Void :: Type`
Uninhabited type.
May be used to convey that a type parameter of an algebraic data type
will not be used. For example, a future of type `Future Void String`
will never be rejected.#### `Any :: Type`
Type comprising every JavaScript value.
Type comprising every Function value.
#### `Arguments :: Type`
Type comprising every [`arguments`][arguments] object.
Constructor for homogeneous Array types.
#### `Array0 :: Type`
Type whose sole member is `[]`.
#### `Array1 :: Type -> Type`
Constructor for singleton Array types.
#### `Array2 :: Type -> Type -> Type`
Constructor for heterogeneous Array types of length 2. `['foo', true]` is
a member of `Array2 String Boolean`.#### `Boolean :: Type`
Type comprising `true` and `false`.
#### `Buffer :: Type`
Type comprising every [Buffer][] object.
#### `Date :: Type`
Type comprising every Date value.
#### `ValidDate :: Type`
Type comprising every [`Date`][] value except `new Date (NaN)`.
#### `Descending :: Type -> Type`
[Descending][] type constructor.
#### `Either :: Type -> Type -> Type`
[Either][] type constructor.
#### `Error :: Type`
Type comprising every Error value, including values of more specific
constructors such as [`SyntaxError`][] and [`TypeError`][].#### `Fn :: Type -> Type -> Type`
Binary type constructor for unary function types. `$.Fn (I) (O)`
represents `I -> O`, the type of functions that take a value of
type `I` and return a value of type `O`.#### `Function :: NonEmpty (Array Type) -> Type`
Constructor for Function types.
Examples:
- `$.Function ([$.Date, $.String])` represents the `Date -> String`
type; and
- `$.Function ([a, b, a])` represents the `(a, b) -> a` type.Type comprising every [HTML element][].
#### `Identity :: Type -> Type`
[Identity][] type constructor.
#### `JsMap :: Type -> Type -> Type`
Constructor for native Map types. `$.JsMap ($.Number) ($.String)`,
for example, is the type comprising every native Map whose keys are
numbers and whose values are strings.Constructor for native Set types. `$.JsSet ($.Number)`, for example,
is the type comprising every native Set whose values are numbers.[Maybe][] type constructor.
#### `Module :: Type`
Type comprising every ES module.
#### `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.
#### `Null :: Type`
Type whose sole member is `null`.
#### `Nullable :: Type -> Type`
Constructor for types that include `null` as a member.
#### `Number :: Type`
Type comprising every primitive Number value (including `NaN`).
Type comprising every [`Number`][] value greater than zero.
Type comprising every [`Number`][] value less than zero.
Type comprising every [`Number`][] value except `NaN`.
#### `NonZeroValidNumber :: Type`
Type comprising every [`ValidNumber`][] value except `0` and `-0`.
Type comprising every [`ValidNumber`][] value except `Infinity` and
`-Infinity`.#### `NonZeroFiniteNumber :: Type`
Type comprising every [`FiniteNumber`][] value except `0` and `-0`.
#### `PositiveFiniteNumber :: Type`
Type comprising every [`FiniteNumber`][] value greater than zero.
#### `NegativeFiniteNumber :: Type`
Type comprising every [`FiniteNumber`][] value less than zero.
#### `Integer :: Type`
Type comprising every integer in the range
[[`Number.MIN_SAFE_INTEGER`][min] .. [`Number.MAX_SAFE_INTEGER`][max]].Type comprising every [`Integer`][] value except `0` and `-0`.
#### `NonNegativeInteger :: Type`
Type comprising every non-negative [`Integer`][] value (including `-0`).
Also known as the set of natural numbers under ISO 80000-2:2009.#### `PositiveInteger :: Type`
Type comprising every [`Integer`][] value greater than zero.
#### `NegativeInteger :: Type`
Type comprising every [`Integer`][] value less than zero.
#### `Object :: Type`
Type comprising every "plain" Object value. Specifically, values
created via:- object literal syntax;
- [`Object.create`][]; or
- the `new` operator in conjunction with `Object` or a custom
constructor function.#### `Pair :: Type -> Type -> Type`
[Pair][] type constructor.
#### `RegExp :: Type`
Type comprising every RegExp value.
Type comprising every [`RegExp`][] value whose `global` flag is `true`.
See also [`NonGlobalRegExp`][].
#### `NonGlobalRegExp :: Type`
Type comprising every [`RegExp`][] value whose `global` flag is `false`.
See also [`GlobalRegExp`][].
#### `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.#### `String :: Type`
Type comprising every primitive String value.
#### `RegexFlags :: Type`
Type comprising the canonical RegExp flags:
- `''`
- `'g'`
- `'i'`
- `'m'`
- `'gi'`
- `'gm'`
- `'im'`
- `'gim'`#### `Symbol :: Type`
Type comprising every Symbol value.
#### `Type :: Type`
Type comprising every `Type` value.
#### `TypeClass :: Type`
Type comprising every [`TypeClass`][] value.
#### `Undefined :: Type`
Type whose sole member is `undefined`.
#### `env :: Array Type`
An array of [types][]:
-
[AnyFunction](#AnyFunction)
-[Arguments](#Arguments)
-[Array](#Array) ([Unknown][])
-[Array2](#Array2) ([Unknown][]) ([Unknown][])
-[Boolean](#Boolean)
-[Buffer](#Buffer)
-[Date](#Date)
-[Descending](#Descending) ([Unknown][])
-[Either](#Either) ([Unknown][]) ([Unknown][])
-[Error](#Error)
-[Fn](#Fn) ([Unknown][]) ([Unknown][])
-[HtmlElement](#HtmlElement)
-[Identity](#Identity) ([Unknown][])
-[JsMap](#JsMap) ([Unknown][]) ([Unknown][])
-[JsSet](#JsSet) ([Unknown][])
-[Maybe](#Maybe) ([Unknown][])
-[Module](#Module)
-[Null](#Null)
-[Number](#Number)
-[Object](#Object)
-[Pair](#Pair) ([Unknown][]) ([Unknown][])
-[RegExp](#RegExp)
-[StrMap](#StrMap) ([Unknown][])
-[String](#String)
-[Symbol](#Symbol)
-[Type](#Type)
-[TypeClass](#TypeClass)
-[Undefined](#Undefined)
#### `test :: Array Type -> Type -> a -> Boolean`
Takes an environment, a type, and any value. Returns `true` if the value
is a member of the type; `false` otherwise.The environment is only significant if the type contains
[type variables][].### Type constructors
sanctuary-def provides several functions for defining types.
#### `NullaryType :: String -> String -> Array Type -> (Any -> Boolean) -> Type`
Type constructor for types with no type variables (such as [`Number`][]).
To define a nullary type `t` one must provide:
- the name of `t` (exposed as `t.name`);
- the documentation URL of `t` (exposed as `t.url`);
- an array of supertypes (exposed as `t.supertypes`); and
- 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:
```javascript
// Integer :: Type
const Integer = $.NullaryType
('Integer')
('http://example.com/my-package#Integer')
([])
(x => typeof x === 'number' &&
Math.floor (x) === x &&
x >= Number.MIN_SAFE_INTEGER &&
x <= Number.MAX_SAFE_INTEGER);// NonZeroInteger :: Type
const NonZeroInteger = $.NullaryType
('NonZeroInteger')
('http://example.com/my-package#NonZeroInteger')
([Integer])
(x => x !== 0);// rem :: Integer -> NonZeroInteger -> Integer
const rem =
def ('rem')
({})
([Integer, NonZeroInteger, Integer])
(x => y => x % y);rem (42) (5);
// => 2rem (0.5);
// ! TypeError: Invalid value
//
// rem :: Integer -> NonZeroInteger -> Integer
// ^^^^^^^
// 1
//
// 1) 0.5 :: Number
//
// The value at position 1 is not a member of ‘Integer’.
//
// See http://example.com/my-package#Integer for information about the Integer type.rem (42) (0);
// ! TypeError: Invalid value
//
// rem :: Integer -> NonZeroInteger -> Integer
// ^^^^^^^^^^^^^^
// 1
//
// 1) 0 :: Number
//
// The value at position 1 is not a member of ‘NonZeroInteger’.
//
// See http://example.com/my-package#NonZeroInteger for information about the NonZeroInteger type.
```Type constructor for types with one type variable (such as [`Array`][]).
To define a unary type `t a` one must provide:
- the name of `t` (exposed as `t.name`);
- the documentation URL of `t` (exposed as `t.url`);
- 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 the values
of type `a` contained in the `t`; and- the type of `a`.
For example:
```javascript
const show = require ('sanctuary-show');
const type = require ('sanctuary-type-identifiers');// maybeTypeIdent :: String
const maybeTypeIdent = 'my-package/Maybe';// Maybe :: Type -> Type
const Maybe = $.UnaryType
('Maybe')
('http://example.com/my-package#Maybe')
([])
(x => type (x) === maybeTypeIdent)
(maybe => maybe.isJust ? [maybe.value] : []);// Nothing :: Maybe a
const Nothing = {
'isJust': false,
'isNothing': true,
'@@type': maybeTypeIdent,
'@@show': () => 'Nothing',
};// Just :: a -> Maybe a
const Just = x => ({
'isJust': true,
'isNothing': false,
'@@type': maybeTypeIdent,
'@@show': () => `Just (${show (x)})`,
'value': x,
});// fromMaybe :: a -> Maybe a -> a
const fromMaybe =
def ('fromMaybe')
({})
([a, Maybe (a), a])
(x => m => m.isJust ? m.value : x);fromMaybe (0) (Just (42));
// => 42fromMaybe (0) (Nothing);
// => 0fromMaybe (0) (Just ('XXX'));
// ! TypeError: Type-variable constraint violation
//
// fromMaybe :: a -> Maybe a -> a
// ^ ^
// 1 2
//
// 1) 0 :: Number
//
// 2) "XXX" :: String
//
// Since there is no type of which all the above values are members, the type-variable constraint has been violated.
```Type constructor for types with two type variables (such as
[`Array2`][]).To define a binary type `t a b` one must provide:
- the name of `t` (exposed as `t.name`);
- the documentation URL of `t` (exposed as `t.url`);
- 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 the
values of type `a` contained in the `t`;- a function that takes any value of type `t a b` and returns the
values of type `b` contained in the `t`;- the type of `a`; and
- the type of `b`.
For example:
```javascript
const type = require ('sanctuary-type-identifiers');// pairTypeIdent :: String
const pairTypeIdent = 'my-package/Pair';// $Pair :: Type -> Type -> Type
const $Pair = $.BinaryType
('Pair')
('http://example.com/my-package#Pair')
([])
(x => type (x) === pairTypeIdent)
(({fst}) => [fst])
(({snd}) => [snd]);// Pair :: a -> b -> Pair a b
const Pair =
def ('Pair')
({})
([a, b, $Pair (a) (b)])
(fst => snd => ({
'fst': fst,
'snd': snd,
'@@type': pairTypeIdent,
'@@show': () => `Pair (${show (fst)}) (${show (snd)})`,
}));// Rank :: Type
const Rank = $.NullaryType
('Rank')
('http://example.com/my-package#Rank')
([$.String])
(x => /^(A|2|3|4|5|6|7|8|9|10|J|Q|K)$/.test (x));// Suit :: Type
const Suit = $.NullaryType
('Suit')
('http://example.com/my-package#Suit')
([$.String])
(x => /^[\u2660\u2663\u2665\u2666]$/.test (x));// Card :: Type
const Card = $Pair (Rank) (Suit);// showCard :: Card -> String
const showCard =
def ('showCard')
({})
([Card, $.String])
(card => card.fst + card.snd);showCard (Pair ('A') ('♠'));
// => 'A♠'showCard (Pair ('X') ('♠'));
// ! TypeError: Invalid value
//
// showCard :: Pair Rank Suit -> String
// ^^^^
// 1
//
// 1) "X" :: String
//
// The value at position 1 is not a member of ‘Rank’.
//
// See http://example.com/my-package#Rank for information about the Rank type.
```#### `EnumType :: String -> String -> Array Any -> Type`
Type constructor for [enumerated types][] (such as [`RegexFlags`][]).
To define an enumerated type `t` one must provide:
- the name of `t` (exposed as `t.name`);
- the documentation URL of `t` (exposed as `t.url`); and
- an array of distinct values.
For example:
```javascript
// Denomination :: Type
const Denomination = $.EnumType
('Denomination')
('http://example.com/my-package#Denomination')
([10, 20, 50, 100, 200]);
```#### `RecordType :: StrMap Type -> Type`
`RecordType` is used to construct anonymous record types. The type
definition specifies the name and type of each required field. A field is
an enumerable property (either an own property or an inherited property).To define an anonymous record type one must provide:
- an object mapping field name to type.
For example:
```javascript
// Point :: Type
const Point = $.RecordType ({x: $.FiniteNumber, y: $.FiniteNumber});// dist :: Point -> Point -> FiniteNumber
const dist =
def ('dist')
({})
([Point, Point, $.FiniteNumber])
(p => q => Math.sqrt (Math.pow (p.x - q.x, 2) +
Math.pow (p.y - q.y, 2)));dist ({x: 0, y: 0}) ({x: 3, y: 4});
// => 5dist ({x: 0, y: 0}) ({x: 3, y: 4, color: 'red'});
// => 5dist ({x: 0, y: 0}) ({x: NaN, y: NaN});
// ! TypeError: Invalid value
//
// dist :: { x :: FiniteNumber, y :: FiniteNumber } -> { x :: FiniteNumber, y :: FiniteNumber } -> FiniteNumber
// ^^^^^^^^^^^^
// 1
//
// 1) NaN :: Number
//
// The value at position 1 is not a member of ‘FiniteNumber’.dist (0);
// ! TypeError: Invalid value
//
// dist :: { x :: FiniteNumber, y :: FiniteNumber } -> { x :: FiniteNumber, y :: FiniteNumber } -> FiniteNumber
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// 1
//
// 1) 0 :: Number
//
// The value at position 1 is not a member of ‘{ x :: FiniteNumber, y :: FiniteNumber }’.
```#### `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
an enumerable property (either an own property or an inherited property).To define a named record type `t` one must provide:
- the name of `t` (exposed as `t.name`);
- 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')
([Circle])
({height: $.PositiveFiniteNumber});// volume :: Cylinder -> PositiveFiniteNumber
const volume =
def ('volume')
({})
([Cylinder, $.FiniteNumber])
(cyl => Math.PI * cyl.radius * cyl.radius * cyl.height);volume ({radius: 2, height: 10});
// => 125.66370614359172volume ({radius: 2});
// ! TypeError: Invalid value
//
// volume :: Cylinder -> FiniteNumber
// ^^^^^^^^
// 1
//
// 1) {"radius": 2} :: Object, StrMap Number
//
// The value at position 1 is not a member of ‘Cylinder’.
//
// See http://example.com/my-package#Cylinder for information about the Cylinder type.
```#### `TypeVariable :: String -> Type`
Polymorphism is powerful. Not being able to define a function for
all types would be very limiting indeed: one couldn't even define the
identity function!Before defining a polymorphic function one must define one or more type
variables:```javascript
const a = $.TypeVariable ('a');
const b = $.TypeVariable ('b');// id :: a -> a
const id = def ('id') ({}) ([a, a]) (x => x);id (42);
// => 42id (null);
// => null
```The same type variable may be used in multiple positions, creating a
constraint:```javascript
// cmp :: a -> a -> Number
const cmp =
def ('cmp')
({})
([a, a, $.Number])
(x => y => x < y ? -1 : x > y ? 1 : 0);cmp (42) (42);
// => 0cmp ('a') ('z');
// => -1cmp ('z') ('a');
// => 1cmp (0) ('1');
// ! TypeError: Type-variable constraint violation
//
// cmp :: a -> a -> Number
// ^ ^
// 1 2
//
// 1) 0 :: Number
//
// 2) "1" :: String
//
// Since there is no type of which all the above values are members, the type-variable constraint has been violated.
```#### `UnaryTypeVariable :: String -> Type -> Type`
Combines [`UnaryType`][] and [`TypeVariable`][].
To define a unary type variable `t a` one must provide:
- a name (conventionally matching `^[a-z]$`); and
- the type of `a`.
Consider the type of a generalized `map`:
```haskell
map :: Functor f => (a -> b) -> f a -> f b
````f` is a unary type variable. With two (nullary) type variables, one
unary type variable, and one [type class][] it's possible to define a
fully polymorphic `map` function:```javascript
const $ = require ('sanctuary-def');
const Z = require ('sanctuary-type-classes');const a = $.TypeVariable ('a');
const b = $.TypeVariable ('b');
const f = $.UnaryTypeVariable ('f');// map :: Functor f => (a -> b) -> f a -> f b
const map =
def ('map')
({f: [Z.Functor]})
([$.Function ([a, b]), f (a), f (b)])
(f => functor => Z.map (f, functor));
```Whereas a regular type variable is fully resolved (`a` might become
`Array (Array String)`, for example), a unary type variable defers to
its type argument, which may itself be a type variable. The type argument
corresponds to the type argument of a unary type or the *second* type
argument of a binary type. The second type argument of `Map k v`, for
example, is `v`. One could replace `Functor => f` with `Map k` or with
`Map Integer`, but not with `Map`.This shallow inspection makes it possible to constrain a value's "outer"
and "inner" types independently.#### `BinaryTypeVariable :: String -> Type -> Type -> Type`
Combines [`BinaryType`][] and [`TypeVariable`][].
To define a binary type variable `t a b` one must provide:
- a name (conventionally matching `^[a-z]$`);
- the type of `a`; and
- the type of `b`.
The more detailed explanation of [`UnaryTypeVariable`][] also applies to
`BinaryTypeVariable`.`$.Thunk (T)` is shorthand for `$.Function ([T])`, the type comprising
every nullary function (thunk) that returns a value of type `T`.#### `Predicate :: Type -> Type`
`$.Predicate (T)` is shorthand for `$.Fn (T) ($.Boolean)`, the type
comprising every predicate function that takes a value of type `T`.### Type classes
One can trivially define a function of type `String -> String -> String`
that concatenates two strings. This is overly restrictive, though, since
other types support concatenation (`Array a`, for example).One could use a type variable to define a polymorphic "concat" function:
```javascript
// _concat :: a -> a -> a
const _concat =
def ('_concat')
({})
([a, a, a])
(x => y => x.concat (y));_concat ('fizz') ('buzz');
// => 'fizzbuzz'_concat ([1, 2]) ([3, 4]);
// => [1, 2, 3, 4]_concat ([1, 2]) ('buzz');
// ! TypeError: Type-variable constraint violation
//
// _concat :: a -> a -> a
// ^ ^
// 1 2
//
// 1) [1, 2] :: Array Number
//
// 2) "buzz" :: String
//
// Since there is no type of which all the above values are members, the type-variable constraint has been violated.
```The type of `_concat` is misleading: it suggests that it can operate on
any two values of *any* one type. In fact there's an implicit constraint,
since the type must support concatenation (in [mathematical][semigroup]
terms, the type must have a [semigroup][FL:Semigroup]). Violating this
implicit constraint results in a run-time error in the implementation:```javascript
_concat (null) (null);
// ! TypeError: Cannot read property 'concat' of null
```The solution is to constrain `a` by first defining a [`TypeClass`][]
value, then specifying the constraint in the definition of the "concat"
function:```javascript
const Z = require ('sanctuary-type-classes');// Semigroup :: TypeClass
const Semigroup = Z.TypeClass (
'my-package/Semigroup',
'http://example.com/my-package#Semigroup',
[],
x => x != null && typeof x.concat === 'function'
);// concat :: Semigroup a => a -> a -> a
const concat =
def ('concat')
({a: [Semigroup]})
([a, a, a])
(x => y => x.concat (y));concat ([1, 2]) ([3, 4]);
// => [1, 2, 3, 4]concat (null) (null);
// ! TypeError: Type-class constraint violation
//
// concat :: Semigroup a => a -> a -> a
// ^^^^^^^^^^^ ^
// 1
//
// 1) null :: Null
//
// ‘concat’ requires ‘a’ to satisfy the Semigroup type-class constraint; the value at position 1 does not.
//
// See http://example.com/my-package#Semigroup for information about the my-package/Semigroup type class.
```Multiple constraints may be placed on a type variable by including
multiple `TypeClass` values in the array (e.g. `{a: [Foo, Bar, Baz]}`).[Buffer]: https://nodejs.org/api/buffer.html#buffer_buffer
[Descending]: https://github.com/sanctuary-js/sanctuary-descending/tree/v2.1.0
[Either]: https://github.com/sanctuary-js/sanctuary-either/tree/v2.1.0
[FL:Semigroup]: https://github.com/fantasyland/fantasy-land#semigroup
[HTML element]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element
[Identity]: https://github.com/sanctuary-js/sanctuary-identity/tree/v2.1.0
[Maybe]: https://github.com/sanctuary-js/sanctuary-maybe/tree/v2.1.0
[Monoid]: https://github.com/fantasyland/fantasy-land#monoid
[Pair]: https://github.com/sanctuary-js/sanctuary-pair/tree/v2.1.0
[Setoid]: https://github.com/fantasyland/fantasy-land#setoid
[Unknown]: #Unknown
[`Array`]: #Array
[`Array2`]: #Array2
[`BinaryType`]: #BinaryType
[`Date`]: #Date
[`FiniteNumber`]: #FiniteNumber
[`GlobalRegExp`]: #GlobalRegExp
[`Integer`]: #Integer
[`NonGlobalRegExp`]: #NonGlobalRegExp
[`Number`]: #Number
[`Object.create`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
[`RegExp`]: #RegExp
[`RegexFlags`]: #RegexFlags
[`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
[`TypeError`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError
[`TypeVariable`]: #TypeVariable
[`UnaryType`]: #UnaryType
[`UnaryTypeVariable`]: #UnaryTypeVariable
[`Unknown`]: #Unknown
[`ValidNumber`]: #ValidNumber
[`env`]: #env
[arguments]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments
[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
[semigroup]: https://en.wikipedia.org/wiki/Semigroup
[type class]: #type-classes
[type variables]: #TypeVariable
[types]: #types