├── .circleci
└── config.yml
├── .config
├── .eslintrc.json
├── .github
├── CONTRIBUTING.md
└── FUNDING.yml
├── .gitignore
├── .npmrc
├── LICENSE
├── README.md
├── index.js
├── package.json
├── scripts
└── prepublish
└── test
├── .eslintrc.json
├── index.js
└── module.js
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 |
3 | orbs:
4 | node: circleci/node@6.3.0
5 |
6 | workflows:
7 | test:
8 | jobs:
9 | - node/test:
10 | setup:
11 | # derive cache key from package.json
12 | - run: cp package.json package-lock.json
13 | override-ci-command: rm package-lock.json && npm install && git checkout -- package.json
14 | matrix:
15 | parameters:
16 | version:
17 | - 18.0.0
18 | - 20.0.0
19 | - 22.0.0
20 |
--------------------------------------------------------------------------------
/.config:
--------------------------------------------------------------------------------
1 | repo-owner = sanctuary-js
2 | repo-name = sanctuary-def
3 | contributing-file = .github/CONTRIBUTING.md
4 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "extends": ["./node_modules/sanctuary-style/eslint.json"],
4 | "parserOptions": {"ecmaVersion": 2020, "sourceType": "module"},
5 | "rules": {
6 | "comma-dangle": ["off"],
7 | "no-param-reassign": ["off"]
8 | },
9 | "overrides": [
10 | {
11 | "files": ["index.js"],
12 | "globals": {"globalThis": "readonly"}
13 | }
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Note: __README.md__ is generated from comments in __index.js__. Do not modify
4 | __README.md__ directly.
5 |
6 | 1. Update local main branch:
7 |
8 | $ git checkout main
9 | $ git pull upstream main
10 |
11 | 2. Create feature branch:
12 |
13 | $ git checkout -b feature-x
14 |
15 | 3. Make one or more atomic commits, and ensure that each commit has a
16 | descriptive commit message. Commit messages should be line wrapped
17 | at 72 characters.
18 |
19 | 4. Run `npm test`, and address any errors. Preferably, fix commits in place
20 | using `git rebase` or `git commit --amend` to make the changes easier to
21 | review.
22 |
23 | 5. Push:
24 |
25 | $ git push origin feature-x
26 |
27 | 6. Open a pull request.
28 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [davidchambers, Avaq]
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /coverage/
2 | /node_modules/
3 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2020 Sanctuary
4 | Copyright (c) 2016 Plaid Technologies, Inc.
5 |
6 | Permission is hereby granted, free of charge, to any person
7 | obtaining a copy of this software and associated documentation
8 | files (the "Software"), to deal in the Software without
9 | restriction, including without limitation the rights to use,
10 | copy, modify, merge, publish, distribute, sublicense, and/or
11 | sell copies of the Software, and to permit persons to whom the
12 | Software is furnished to do so, subject to the following
13 | conditions:
14 |
15 | The above copyright notice and this permission notice shall be
16 | included in all copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25 | OTHER DEALINGS IN THE SOFTWARE.
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # sanctuary-def
2 |
3 | sanctuary-def is a run-time type system for JavaScript. It facilitates
4 | the definition of curried JavaScript functions that are explicit about
5 | the number of arguments to which they may be applied and the types of
6 | those arguments.
7 |
8 | It is conventional to import the package as `$`:
9 |
10 | ```javascript
11 | const $ = require ('sanctuary-def');
12 | ```
13 |
14 | The next step is to define an environment. An environment is an array
15 | of [types][]. [`env`][] is an environment containing all the built-in
16 | JavaScript types. It may be used as the basis for environments that
17 | include custom types in addition to the built-in types:
18 |
19 | ```javascript
20 | // Integer :: Type
21 | const Integer = '...';
22 |
23 | // NonZeroInteger :: Type
24 | const NonZeroInteger = '...';
25 |
26 | // env :: Array Type
27 | const env = $.env.concat ([Integer, NonZeroInteger]);
28 | ```
29 |
30 | Type constructors such as `List :: Type -> Type` cannot be included in
31 | an environment as they're not of the correct type. One could, though,
32 | use a type constructor to define a fixed number of concrete types:
33 |
34 | ```javascript
35 | // env :: Array Type
36 | const env = $.env.concat ([
37 | List ($.Number), // :: Type
38 | List ($.String), // :: Type
39 | List (List ($.Number)), // :: Type
40 | List (List ($.String)), // :: Type
41 | List (List (List ($.Number))), // :: Type
42 | List (List (List ($.String))), // :: Type
43 | ]);
44 | ```
45 |
46 | Not only would this be tedious, but one could never enumerate all possible
47 | types as there are infinitely many. Instead, one should use [`Unknown`][]:
48 |
49 | ```javascript
50 | // env :: Array Type
51 | const env = $.env.concat ([List ($.Unknown)]);
52 | ```
53 |
54 | The next step is to define a `def` function for the environment using
55 | `$.create`:
56 |
57 | ```javascript
58 | // def :: String -> StrMap (Array TypeClass) -> Array Type -> Function -> Function
59 | const def = $.create ({checkTypes: true, env});
60 | ```
61 |
62 | The `checkTypes` option determines whether type checking is enabled.
63 | This allows one to only pay the performance cost of run-time type checking
64 | during development. For example:
65 |
66 | ```javascript
67 | // def :: String -> StrMap (Array TypeClass) -> Array Type -> Function -> Function
68 | const def = $.create ({
69 | checkTypes: process.env.NODE_ENV === 'development',
70 | env,
71 | });
72 | ```
73 |
74 | `def` is a function for defining functions. For example:
75 |
76 | ```javascript
77 | // add :: Number -> Number -> Number
78 | const add =
79 | def ('add') // name
80 | ({}) // type-class constraints
81 | ([$.Number, $.Number, $.Number]) // input and output types
82 | (x => y => x + y); // implementation
83 | ```
84 |
85 | `[$.Number, $.Number, $.Number]` specifies that `add` takes two arguments
86 | of type `Number`, one at a time, and returns a value of type `Number`.
87 |
88 | Applying `add` to two arguments, one at a time, gives the expected result:
89 |
90 | ```javascript
91 | add (2) (2);
92 | // => 4
93 | ```
94 |
95 | Applying `add` to multiple arguments at once results in an exception being
96 | thrown:
97 |
98 | ```javascript
99 | add (2, 2, 2);
100 | // ! TypeError: ‘add’ applied to the wrong number of arguments
101 | //
102 | // add :: Number -> Number -> Number
103 | // ^^^^^^
104 | // 1
105 | //
106 | // Expected one argument but received three arguments:
107 | //
108 | // - 2
109 | // - 2
110 | // - 2
111 | ```
112 |
113 | Applying `add` to one argument produces a function awaiting the remaining
114 | argument. This is known as partial application. Partial application allows
115 | more specific functions to be defined in terms of more general ones:
116 |
117 | ```javascript
118 | // inc :: Number -> Number
119 | const inc = add (1);
120 |
121 | inc (7);
122 | // => 8
123 | ```
124 |
125 | JavaScript's implicit type coercion often obfuscates the source of type
126 | errors. Consider the following function:
127 |
128 | ```javascript
129 | // _add :: Number -> Number -> Number
130 | const _add = x => y => x + y;
131 | ```
132 |
133 | The type signature indicates that `_add` takes arguments of type `Number`,
134 | but this is not enforced. This allows type errors to be silently ignored:
135 |
136 | ```javascript
137 | _add ('2') ('2');
138 | // => '22'
139 | ```
140 |
141 | `add`, on the other hand, throws if applied to arguments of the wrong
142 | types:
143 |
144 | ```javascript
145 | add ('2') ('2');
146 | // ! TypeError: Invalid value
147 | //
148 | // add :: Number -> Number -> Number
149 | // ^^^^^^
150 | // 1
151 | //
152 | // 1) "2" :: String
153 | //
154 | // The value at position 1 is not a member of ‘Number’.
155 | ```
156 |
157 | Type checking is performed as arguments are provided (rather than once all
158 | arguments have been provided), so type errors are reported early:
159 |
160 | ```javascript
161 | add ('X');
162 | // ! TypeError: Invalid value
163 | //
164 | // add :: Number -> Number -> Number
165 | // ^^^^^^
166 | // 1
167 | //
168 | // 1) "X" :: String
169 | //
170 | // The value at position 1 is not a member of ‘Number’.
171 | ```
172 |
173 | ### Types
174 |
175 | Conceptually, a type is a set of values. One can think of a value of
176 | type `Type` as a function of type `Any -> Boolean` that tests values
177 | for membership in the set (though this is an oversimplification).
178 |
179 | #### `Unknown :: Type`
180 |
181 | Type used to represent missing type information. The type of `[]`,
182 | for example, is `Array ???`.
183 |
184 | May be used with type constructors when defining environments. Given a
185 | type constructor `List :: Type -> Type`, one could use `List ($.Unknown)`
186 | to include an infinite number of types in an environment:
187 |
188 | - `List Number`
189 | - `List String`
190 | - `List (List Number)`
191 | - `List (List String)`
192 | - `List (List (List Number))`
193 | - `List (List (List String))`
194 | - `...`
195 |
196 | #### `Void :: Type`
197 |
198 | Uninhabited type.
199 |
200 | May be used to convey that a type parameter of an algebraic data type
201 | will not be used. For example, a future of type `Future Void String`
202 | will never be rejected.
203 |
204 | #### `Any :: Type`
205 |
206 | Type comprising every JavaScript value.
207 |
208 | #### `AnyFunction :: Type`
209 |
210 | Type comprising every Function value.
211 |
212 | #### `Arguments :: Type`
213 |
214 | Type comprising every [`arguments`][arguments] object.
215 |
216 | #### `Array :: Type -> Type`
217 |
218 | Constructor for homogeneous Array types.
219 |
220 | #### `Array0 :: Type`
221 |
222 | Type whose sole member is `[]`.
223 |
224 | #### `Array1 :: Type -> Type`
225 |
226 | Constructor for singleton Array types.
227 |
228 | #### `Array2 :: Type -> Type -> Type`
229 |
230 | Constructor for heterogeneous Array types of length 2. `['foo', true]` is
231 | a member of `Array2 String Boolean`.
232 |
233 | #### `Boolean :: Type`
234 |
235 | Type comprising `true` and `false`.
236 |
237 | #### `Buffer :: Type`
238 |
239 | Type comprising every [Buffer][] object.
240 |
241 | #### `Date :: Type`
242 |
243 | Type comprising every Date value.
244 |
245 | #### `ValidDate :: Type`
246 |
247 | Type comprising every [`Date`][] value except `new Date (NaN)`.
248 |
249 | #### `Descending :: Type -> Type`
250 |
251 | [Descending][] type constructor.
252 |
253 | #### `Either :: Type -> Type -> Type`
254 |
255 | [Either][] type constructor.
256 |
257 | #### `Error :: Type`
258 |
259 | Type comprising every Error value, including values of more specific
260 | constructors such as [`SyntaxError`][] and [`TypeError`][].
261 |
262 | #### `Fn :: Type -> Type -> Type`
263 |
264 | Binary type constructor for unary function types. `$.Fn (I) (O)`
265 | represents `I -> O`, the type of functions that take a value of
266 | type `I` and return a value of type `O`.
267 |
268 | #### `Function :: NonEmpty (Array Type) -> Type`
269 |
270 | Constructor for Function types.
271 |
272 | Examples:
273 |
274 | - `$.Function ([$.Date, $.String])` represents the `Date -> String`
275 | type; and
276 | - `$.Function ([a, b, a])` represents the `(a, b) -> a` type.
277 |
278 | #### `HtmlElement :: Type`
279 |
280 | Type comprising every [HTML element][].
281 |
282 | #### `Identity :: Type -> Type`
283 |
284 | [Identity][] type constructor.
285 |
286 | #### `JsMap :: Type -> Type -> Type`
287 |
288 | Constructor for native Map types. `$.JsMap ($.Number) ($.String)`,
289 | for example, is the type comprising every native Map whose keys are
290 | numbers and whose values are strings.
291 |
292 | #### `JsSet :: Type -> Type`
293 |
294 | Constructor for native Set types. `$.JsSet ($.Number)`, for example,
295 | is the type comprising every native Set whose values are numbers.
296 |
297 | #### `Maybe :: Type -> Type`
298 |
299 | [Maybe][] type constructor.
300 |
301 | #### `Module :: Type`
302 |
303 | Type comprising every ES module.
304 |
305 | #### `NonEmpty :: Type -> Type`
306 |
307 | Constructor for non-empty types. `$.NonEmpty ($.String)`, for example, is
308 | the type comprising every [`String`][] value except `''`.
309 |
310 | The given type must satisfy the [Monoid][] and [Setoid][] specifications.
311 |
312 | #### `Null :: Type`
313 |
314 | Type whose sole member is `null`.
315 |
316 | #### `Nullable :: Type -> Type`
317 |
318 | Constructor for types that include `null` as a member.
319 |
320 | #### `Number :: Type`
321 |
322 | Type comprising every primitive Number value (including `NaN`).
323 |
324 | #### `PositiveNumber :: Type`
325 |
326 | Type comprising every [`Number`][] value greater than zero.
327 |
328 | #### `NegativeNumber :: Type`
329 |
330 | Type comprising every [`Number`][] value less than zero.
331 |
332 | #### `ValidNumber :: Type`
333 |
334 | Type comprising every [`Number`][] value except `NaN`.
335 |
336 | #### `NonZeroValidNumber :: Type`
337 |
338 | Type comprising every [`ValidNumber`][] value except `0` and `-0`.
339 |
340 | #### `FiniteNumber :: Type`
341 |
342 | Type comprising every [`ValidNumber`][] value except `Infinity` and
343 | `-Infinity`.
344 |
345 | #### `NonZeroFiniteNumber :: Type`
346 |
347 | Type comprising every [`FiniteNumber`][] value except `0` and `-0`.
348 |
349 | #### `PositiveFiniteNumber :: Type`
350 |
351 | Type comprising every [`FiniteNumber`][] value greater than zero.
352 |
353 | #### `NegativeFiniteNumber :: Type`
354 |
355 | Type comprising every [`FiniteNumber`][] value less than zero.
356 |
357 | #### `Integer :: Type`
358 |
359 | Type comprising every integer in the range
360 | [[`Number.MIN_SAFE_INTEGER`][min] .. [`Number.MAX_SAFE_INTEGER`][max]].
361 |
362 | #### `NonZeroInteger :: Type`
363 |
364 | Type comprising every [`Integer`][] value except `0` and `-0`.
365 |
366 | #### `NonNegativeInteger :: Type`
367 |
368 | Type comprising every non-negative [`Integer`][] value (including `-0`).
369 | Also known as the set of natural numbers under ISO 80000-2:2009.
370 |
371 | #### `PositiveInteger :: Type`
372 |
373 | Type comprising every [`Integer`][] value greater than zero.
374 |
375 | #### `NegativeInteger :: Type`
376 |
377 | Type comprising every [`Integer`][] value less than zero.
378 |
379 | #### `Object :: Type`
380 |
381 | Type comprising every "plain" Object value. Specifically, values
382 | created via:
383 |
384 | - object literal syntax;
385 | - [`Object.create`][]; or
386 | - the `new` operator in conjunction with `Object` or a custom
387 | constructor function.
388 |
389 | #### `Pair :: Type -> Type -> Type`
390 |
391 | [Pair][] type constructor.
392 |
393 | #### `RegExp :: Type`
394 |
395 | Type comprising every RegExp value.
396 |
397 | #### `GlobalRegExp :: Type`
398 |
399 | Type comprising every [`RegExp`][] value whose `global` flag is `true`.
400 |
401 | See also [`NonGlobalRegExp`][].
402 |
403 | #### `NonGlobalRegExp :: Type`
404 |
405 | Type comprising every [`RegExp`][] value whose `global` flag is `false`.
406 |
407 | See also [`GlobalRegExp`][].
408 |
409 | #### `StrMap :: Type -> Type`
410 |
411 | Constructor for homogeneous Object types.
412 |
413 | `{foo: 1, bar: 2, baz: 3}`, for example, is a member of `StrMap Number`;
414 | `{foo: 1, bar: 2, baz: 'XXX'}` is not.
415 |
416 | #### `String :: Type`
417 |
418 | Type comprising every primitive String value.
419 |
420 | #### `RegexFlags :: Type`
421 |
422 | Type comprising the canonical RegExp flags:
423 |
424 | - `''`
425 | - `'g'`
426 | - `'i'`
427 | - `'m'`
428 | - `'gi'`
429 | - `'gm'`
430 | - `'im'`
431 | - `'gim'`
432 |
433 | #### `Symbol :: Type`
434 |
435 | Type comprising every Symbol value.
436 |
437 | #### `Type :: Type`
438 |
439 | Type comprising every `Type` value.
440 |
441 | #### `TypeClass :: Type`
442 |
443 | Type comprising every [`TypeClass`][] value.
444 |
445 | #### `Undefined :: Type`
446 |
447 | Type whose sole member is `undefined`.
448 |
449 | #### `env :: Array Type`
450 |
451 | An array of [types][]:
452 |
453 | - [AnyFunction](#AnyFunction)
454 | - [Arguments](#Arguments)
455 | - [Array](#Array) ([Unknown][])
456 | - [Array2](#Array2) ([Unknown][]) ([Unknown][])
457 | - [Boolean](#Boolean)
458 | - [Buffer](#Buffer)
459 | - [Date](#Date)
460 | - [Descending](#Descending) ([Unknown][])
461 | - [Either](#Either) ([Unknown][]) ([Unknown][])
462 | - [Error](#Error)
463 | - [Fn](#Fn) ([Unknown][]) ([Unknown][])
464 | - [HtmlElement](#HtmlElement)
465 | - [Identity](#Identity) ([Unknown][])
466 | - [JsMap](#JsMap) ([Unknown][]) ([Unknown][])
467 | - [JsSet](#JsSet) ([Unknown][])
468 | - [Maybe](#Maybe) ([Unknown][])
469 | - [Module](#Module)
470 | - [Null](#Null)
471 | - [Number](#Number)
472 | - [Object](#Object)
473 | - [Pair](#Pair) ([Unknown][]) ([Unknown][])
474 | - [RegExp](#RegExp)
475 | - [StrMap](#StrMap) ([Unknown][])
476 | - [String](#String)
477 | - [Symbol](#Symbol)
478 | - [Type](#Type)
479 | - [TypeClass](#TypeClass)
480 | - [Undefined](#Undefined)
481 |
482 | #### `test :: Array Type -> Type -> a -> Boolean`
483 |
484 | Takes an environment, a type, and any value. Returns `true` if the value
485 | is a member of the type; `false` otherwise.
486 |
487 | The environment is only significant if the type contains
488 | [type variables][].
489 |
490 | ### Type constructors
491 |
492 | sanctuary-def provides several functions for defining types.
493 |
494 | #### `NullaryType :: String -> String -> Array Type -> (Any -> Boolean) -> Type`
495 |
496 | Type constructor for types with no type variables (such as [`Number`][]).
497 |
498 | To define a nullary type `t` one must provide:
499 |
500 | - the name of `t` (exposed as `t.name`);
501 |
502 | - the documentation URL of `t` (exposed as `t.url`);
503 |
504 | - an array of supertypes (exposed as `t.supertypes`); and
505 |
506 | - a predicate that accepts any value that is a member of every one of
507 | the given supertypes, and returns `true` if (and only if) the value
508 | is a member of `t`.
509 |
510 | For example:
511 |
512 | ```javascript
513 | // Integer :: Type
514 | const Integer = $.NullaryType
515 | ('Integer')
516 | ('http://example.com/my-package#Integer')
517 | ([])
518 | (x => typeof x === 'number' &&
519 | Math.floor (x) === x &&
520 | x >= Number.MIN_SAFE_INTEGER &&
521 | x <= Number.MAX_SAFE_INTEGER);
522 |
523 | // NonZeroInteger :: Type
524 | const NonZeroInteger = $.NullaryType
525 | ('NonZeroInteger')
526 | ('http://example.com/my-package#NonZeroInteger')
527 | ([Integer])
528 | (x => x !== 0);
529 |
530 | // rem :: Integer -> NonZeroInteger -> Integer
531 | const rem =
532 | def ('rem')
533 | ({})
534 | ([Integer, NonZeroInteger, Integer])
535 | (x => y => x % y);
536 |
537 | rem (42) (5);
538 | // => 2
539 |
540 | rem (0.5);
541 | // ! TypeError: Invalid value
542 | //
543 | // rem :: Integer -> NonZeroInteger -> Integer
544 | // ^^^^^^^
545 | // 1
546 | //
547 | // 1) 0.5 :: Number
548 | //
549 | // The value at position 1 is not a member of ‘Integer’.
550 | //
551 | // See http://example.com/my-package#Integer for information about the Integer type.
552 |
553 | rem (42) (0);
554 | // ! TypeError: Invalid value
555 | //
556 | // rem :: Integer -> NonZeroInteger -> Integer
557 | // ^^^^^^^^^^^^^^
558 | // 1
559 | //
560 | // 1) 0 :: Number
561 | //
562 | // The value at position 1 is not a member of ‘NonZeroInteger’.
563 | //
564 | // See http://example.com/my-package#NonZeroInteger for information about the NonZeroInteger type.
565 | ```
566 |
567 | #### `UnaryType :: Foldable f => String -> String -> Array Type -> (Any -> Boolean) -> (t a -> f a) -> Type -> Type`
568 |
569 | Type constructor for types with one type variable (such as [`Array`][]).
570 |
571 | To define a unary type `t a` one must provide:
572 |
573 | - the name of `t` (exposed as `t.name`);
574 |
575 | - the documentation URL of `t` (exposed as `t.url`);
576 |
577 | - an array of supertypes (exposed as `t.supertypes`);
578 |
579 | - a predicate that accepts any value that is a member of every one of
580 | the given supertypes, and returns `true` if (and only if) the value
581 | is a member of `t x` for some type `x`;
582 |
583 | - a function that takes any value of type `t a` and returns the values
584 | of type `a` contained in the `t`; and
585 |
586 | - the type of `a`.
587 |
588 | For example:
589 |
590 | ```javascript
591 | const show = require ('sanctuary-show');
592 | const type = require ('sanctuary-type-identifiers');
593 |
594 | // maybeTypeIdent :: String
595 | const maybeTypeIdent = 'my-package/Maybe';
596 |
597 | // Maybe :: Type -> Type
598 | const Maybe = $.UnaryType
599 | ('Maybe')
600 | ('http://example.com/my-package#Maybe')
601 | ([])
602 | (x => type (x) === maybeTypeIdent)
603 | (maybe => maybe.isJust ? [maybe.value] : []);
604 |
605 | // Nothing :: Maybe a
606 | const Nothing = {
607 | 'isJust': false,
608 | 'isNothing': true,
609 | '@@type': maybeTypeIdent,
610 | '@@show': () => 'Nothing',
611 | };
612 |
613 | // Just :: a -> Maybe a
614 | const Just = x => ({
615 | 'isJust': true,
616 | 'isNothing': false,
617 | '@@type': maybeTypeIdent,
618 | '@@show': () => `Just (${show (x)})`,
619 | 'value': x,
620 | });
621 |
622 | // fromMaybe :: a -> Maybe a -> a
623 | const fromMaybe =
624 | def ('fromMaybe')
625 | ({})
626 | ([a, Maybe (a), a])
627 | (x => m => m.isJust ? m.value : x);
628 |
629 | fromMaybe (0) (Just (42));
630 | // => 42
631 |
632 | fromMaybe (0) (Nothing);
633 | // => 0
634 |
635 | fromMaybe (0) (Just ('XXX'));
636 | // ! TypeError: Type-variable constraint violation
637 | //
638 | // fromMaybe :: a -> Maybe a -> a
639 | // ^ ^
640 | // 1 2
641 | //
642 | // 1) 0 :: Number
643 | //
644 | // 2) "XXX" :: String
645 | //
646 | // Since there is no type of which all the above values are members, the type-variable constraint has been violated.
647 | ```
648 |
649 | #### `BinaryType :: Foldable f => String -> String -> Array Type -> (Any -> Boolean) -> (t a b -> f a) -> (t a b -> f b) -> Type -> Type -> Type`
650 |
651 | Type constructor for types with two type variables (such as
652 | [`Array2`][]).
653 |
654 | To define a binary type `t a b` one must provide:
655 |
656 | - the name of `t` (exposed as `t.name`);
657 |
658 | - the documentation URL of `t` (exposed as `t.url`);
659 |
660 | - an array of supertypes (exposed as `t.supertypes`);
661 |
662 | - a predicate that accepts any value that is a member of every one of
663 | the given supertypes, and returns `true` if (and only if) the value
664 | is a member of `t x y` for some types `x` and `y`;
665 |
666 | - a function that takes any value of type `t a b` and returns the
667 | values of type `a` contained in the `t`;
668 |
669 | - a function that takes any value of type `t a b` and returns the
670 | values of type `b` contained in the `t`;
671 |
672 | - the type of `a`; and
673 |
674 | - the type of `b`.
675 |
676 | For example:
677 |
678 | ```javascript
679 | const type = require ('sanctuary-type-identifiers');
680 |
681 | // pairTypeIdent :: String
682 | const pairTypeIdent = 'my-package/Pair';
683 |
684 | // $Pair :: Type -> Type -> Type
685 | const $Pair = $.BinaryType
686 | ('Pair')
687 | ('http://example.com/my-package#Pair')
688 | ([])
689 | (x => type (x) === pairTypeIdent)
690 | (({fst}) => [fst])
691 | (({snd}) => [snd]);
692 |
693 | // Pair :: a -> b -> Pair a b
694 | const Pair =
695 | def ('Pair')
696 | ({})
697 | ([a, b, $Pair (a) (b)])
698 | (fst => snd => ({
699 | 'fst': fst,
700 | 'snd': snd,
701 | '@@type': pairTypeIdent,
702 | '@@show': () => `Pair (${show (fst)}) (${show (snd)})`,
703 | }));
704 |
705 | // Rank :: Type
706 | const Rank = $.NullaryType
707 | ('Rank')
708 | ('http://example.com/my-package#Rank')
709 | ([$.String])
710 | (x => /^(A|2|3|4|5|6|7|8|9|10|J|Q|K)$/.test (x));
711 |
712 | // Suit :: Type
713 | const Suit = $.NullaryType
714 | ('Suit')
715 | ('http://example.com/my-package#Suit')
716 | ([$.String])
717 | (x => /^[\u2660\u2663\u2665\u2666]$/.test (x));
718 |
719 | // Card :: Type
720 | const Card = $Pair (Rank) (Suit);
721 |
722 | // showCard :: Card -> String
723 | const showCard =
724 | def ('showCard')
725 | ({})
726 | ([Card, $.String])
727 | (card => card.fst + card.snd);
728 |
729 | showCard (Pair ('A') ('♠'));
730 | // => 'A♠'
731 |
732 | showCard (Pair ('X') ('♠'));
733 | // ! TypeError: Invalid value
734 | //
735 | // showCard :: Pair Rank Suit -> String
736 | // ^^^^
737 | // 1
738 | //
739 | // 1) "X" :: String
740 | //
741 | // The value at position 1 is not a member of ‘Rank’.
742 | //
743 | // See http://example.com/my-package#Rank for information about the Rank type.
744 | ```
745 |
746 | #### `EnumType :: String -> String -> Array Any -> Type`
747 |
748 | Type constructor for [enumerated types][] (such as [`RegexFlags`][]).
749 |
750 | To define an enumerated type `t` one must provide:
751 |
752 | - the name of `t` (exposed as `t.name`);
753 |
754 | - the documentation URL of `t` (exposed as `t.url`); and
755 |
756 | - an array of distinct values.
757 |
758 | For example:
759 |
760 | ```javascript
761 | // Denomination :: Type
762 | const Denomination = $.EnumType
763 | ('Denomination')
764 | ('http://example.com/my-package#Denomination')
765 | ([10, 20, 50, 100, 200]);
766 | ```
767 |
768 | #### `RecordType :: StrMap Type -> Type`
769 |
770 | `RecordType` is used to construct anonymous record types. The type
771 | definition specifies the name and type of each required field. A field is
772 | an enumerable property (either an own property or an inherited property).
773 |
774 | To define an anonymous record type one must provide:
775 |
776 | - an object mapping field name to type.
777 |
778 | For example:
779 |
780 | ```javascript
781 | // Point :: Type
782 | const Point = $.RecordType ({x: $.FiniteNumber, y: $.FiniteNumber});
783 |
784 | // dist :: Point -> Point -> FiniteNumber
785 | const dist =
786 | def ('dist')
787 | ({})
788 | ([Point, Point, $.FiniteNumber])
789 | (p => q => Math.sqrt (Math.pow (p.x - q.x, 2) +
790 | Math.pow (p.y - q.y, 2)));
791 |
792 | dist ({x: 0, y: 0}) ({x: 3, y: 4});
793 | // => 5
794 |
795 | dist ({x: 0, y: 0}) ({x: 3, y: 4, color: 'red'});
796 | // => 5
797 |
798 | dist ({x: 0, y: 0}) ({x: NaN, y: NaN});
799 | // ! TypeError: Invalid value
800 | //
801 | // dist :: { x :: FiniteNumber, y :: FiniteNumber } -> { x :: FiniteNumber, y :: FiniteNumber } -> FiniteNumber
802 | // ^^^^^^^^^^^^
803 | // 1
804 | //
805 | // 1) NaN :: Number
806 | //
807 | // The value at position 1 is not a member of ‘FiniteNumber’.
808 |
809 | dist (0);
810 | // ! TypeError: Invalid value
811 | //
812 | // dist :: { x :: FiniteNumber, y :: FiniteNumber } -> { x :: FiniteNumber, y :: FiniteNumber } -> FiniteNumber
813 | // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
814 | // 1
815 | //
816 | // 1) 0 :: Number
817 | //
818 | // The value at position 1 is not a member of ‘{ x :: FiniteNumber, y :: FiniteNumber }’.
819 | ```
820 |
821 | #### `NamedRecordType :: NonEmpty String -> String -> Array Type -> StrMap Type -> Type`
822 |
823 | `NamedRecordType` is used to construct named record types. The type
824 | definition specifies the name and type of each required field. A field is
825 | an enumerable property (either an own property or an inherited property).
826 |
827 | To define a named record type `t` one must provide:
828 |
829 | - the name of `t` (exposed as `t.name`);
830 |
831 | - the documentation URL of `t` (exposed as `t.url`);
832 |
833 | - an array of supertypes (exposed as `t.supertypes`); and
834 |
835 | - an object mapping field name to type.
836 |
837 | For example:
838 |
839 | ```javascript
840 | // Circle :: Type
841 | const Circle = $.NamedRecordType
842 | ('my-package/Circle')
843 | ('http://example.com/my-package#Circle')
844 | ([])
845 | ({radius: $.PositiveFiniteNumber});
846 |
847 | // Cylinder :: Type
848 | const Cylinder = $.NamedRecordType
849 | ('Cylinder')
850 | ('http://example.com/my-package#Cylinder')
851 | ([Circle])
852 | ({height: $.PositiveFiniteNumber});
853 |
854 | // volume :: Cylinder -> PositiveFiniteNumber
855 | const volume =
856 | def ('volume')
857 | ({})
858 | ([Cylinder, $.FiniteNumber])
859 | (cyl => Math.PI * cyl.radius * cyl.radius * cyl.height);
860 |
861 | volume ({radius: 2, height: 10});
862 | // => 125.66370614359172
863 |
864 | volume ({radius: 2});
865 | // ! TypeError: Invalid value
866 | //
867 | // volume :: Cylinder -> FiniteNumber
868 | // ^^^^^^^^
869 | // 1
870 | //
871 | // 1) {"radius": 2} :: Object, StrMap Number
872 | //
873 | // The value at position 1 is not a member of ‘Cylinder’.
874 | //
875 | // See http://example.com/my-package#Cylinder for information about the Cylinder type.
876 | ```
877 |
878 | #### `TypeVariable :: String -> Type`
879 |
880 | Polymorphism is powerful. Not being able to define a function for
881 | all types would be very limiting indeed: one couldn't even define the
882 | identity function!
883 |
884 | Before defining a polymorphic function one must define one or more type
885 | variables:
886 |
887 | ```javascript
888 | const a = $.TypeVariable ('a');
889 | const b = $.TypeVariable ('b');
890 |
891 | // id :: a -> a
892 | const id = def ('id') ({}) ([a, a]) (x => x);
893 |
894 | id (42);
895 | // => 42
896 |
897 | id (null);
898 | // => null
899 | ```
900 |
901 | The same type variable may be used in multiple positions, creating a
902 | constraint:
903 |
904 | ```javascript
905 | // cmp :: a -> a -> Number
906 | const cmp =
907 | def ('cmp')
908 | ({})
909 | ([a, a, $.Number])
910 | (x => y => x < y ? -1 : x > y ? 1 : 0);
911 |
912 | cmp (42) (42);
913 | // => 0
914 |
915 | cmp ('a') ('z');
916 | // => -1
917 |
918 | cmp ('z') ('a');
919 | // => 1
920 |
921 | cmp (0) ('1');
922 | // ! TypeError: Type-variable constraint violation
923 | //
924 | // cmp :: a -> a -> Number
925 | // ^ ^
926 | // 1 2
927 | //
928 | // 1) 0 :: Number
929 | //
930 | // 2) "1" :: String
931 | //
932 | // Since there is no type of which all the above values are members, the type-variable constraint has been violated.
933 | ```
934 |
935 | #### `UnaryTypeVariable :: String -> Type -> Type`
936 |
937 | Combines [`UnaryType`][] and [`TypeVariable`][].
938 |
939 | To define a unary type variable `t a` one must provide:
940 |
941 | - a name (conventionally matching `^[a-z]$`); and
942 |
943 | - the type of `a`.
944 |
945 | Consider the type of a generalized `map`:
946 |
947 | ```haskell
948 | map :: Functor f => (a -> b) -> f a -> f b
949 | ```
950 |
951 | `f` is a unary type variable. With two (nullary) type variables, one
952 | unary type variable, and one [type class][] it's possible to define a
953 | fully polymorphic `map` function:
954 |
955 | ```javascript
956 | const $ = require ('sanctuary-def');
957 | const Z = require ('sanctuary-type-classes');
958 |
959 | const a = $.TypeVariable ('a');
960 | const b = $.TypeVariable ('b');
961 | const f = $.UnaryTypeVariable ('f');
962 |
963 | // map :: Functor f => (a -> b) -> f a -> f b
964 | const map =
965 | def ('map')
966 | ({f: [Z.Functor]})
967 | ([$.Function ([a, b]), f (a), f (b)])
968 | (f => functor => Z.map (f, functor));
969 | ```
970 |
971 | Whereas a regular type variable is fully resolved (`a` might become
972 | `Array (Array String)`, for example), a unary type variable defers to
973 | its type argument, which may itself be a type variable. The type argument
974 | corresponds to the type argument of a unary type or the *second* type
975 | argument of a binary type. The second type argument of `Map k v`, for
976 | example, is `v`. One could replace `Functor => f` with `Map k` or with
977 | `Map Integer`, but not with `Map`.
978 |
979 | This shallow inspection makes it possible to constrain a value's "outer"
980 | and "inner" types independently.
981 |
982 | #### `BinaryTypeVariable :: String -> Type -> Type -> Type`
983 |
984 | Combines [`BinaryType`][] and [`TypeVariable`][].
985 |
986 | To define a binary type variable `t a b` one must provide:
987 |
988 | - a name (conventionally matching `^[a-z]$`);
989 |
990 | - the type of `a`; and
991 |
992 | - the type of `b`.
993 |
994 | The more detailed explanation of [`UnaryTypeVariable`][] also applies to
995 | `BinaryTypeVariable`.
996 |
997 | #### `Thunk :: Type -> Type`
998 |
999 | `$.Thunk (T)` is shorthand for `$.Function ([T])`, the type comprising
1000 | every nullary function (thunk) that returns a value of type `T`.
1001 |
1002 | #### `Predicate :: Type -> Type`
1003 |
1004 | `$.Predicate (T)` is shorthand for `$.Fn (T) ($.Boolean)`, the type
1005 | comprising every predicate function that takes a value of type `T`.
1006 |
1007 | ### Type classes
1008 |
1009 | One can trivially define a function of type `String -> String -> String`
1010 | that concatenates two strings. This is overly restrictive, though, since
1011 | other types support concatenation (`Array a`, for example).
1012 |
1013 | One could use a type variable to define a polymorphic "concat" function:
1014 |
1015 | ```javascript
1016 | // _concat :: a -> a -> a
1017 | const _concat =
1018 | def ('_concat')
1019 | ({})
1020 | ([a, a, a])
1021 | (x => y => x.concat (y));
1022 |
1023 | _concat ('fizz') ('buzz');
1024 | // => 'fizzbuzz'
1025 |
1026 | _concat ([1, 2]) ([3, 4]);
1027 | // => [1, 2, 3, 4]
1028 |
1029 | _concat ([1, 2]) ('buzz');
1030 | // ! TypeError: Type-variable constraint violation
1031 | //
1032 | // _concat :: a -> a -> a
1033 | // ^ ^
1034 | // 1 2
1035 | //
1036 | // 1) [1, 2] :: Array Number
1037 | //
1038 | // 2) "buzz" :: String
1039 | //
1040 | // Since there is no type of which all the above values are members, the type-variable constraint has been violated.
1041 | ```
1042 |
1043 | The type of `_concat` is misleading: it suggests that it can operate on
1044 | any two values of *any* one type. In fact there's an implicit constraint,
1045 | since the type must support concatenation (in [mathematical][semigroup]
1046 | terms, the type must have a [semigroup][FL:Semigroup]). Violating this
1047 | implicit constraint results in a run-time error in the implementation:
1048 |
1049 | ```javascript
1050 | _concat (null) (null);
1051 | // ! TypeError: Cannot read property 'concat' of null
1052 | ```
1053 |
1054 | The solution is to constrain `a` by first defining a [`TypeClass`][]
1055 | value, then specifying the constraint in the definition of the "concat"
1056 | function:
1057 |
1058 | ```javascript
1059 | const Z = require ('sanctuary-type-classes');
1060 |
1061 | // Semigroup :: TypeClass
1062 | const Semigroup = Z.TypeClass (
1063 | 'my-package/Semigroup',
1064 | 'http://example.com/my-package#Semigroup',
1065 | [],
1066 | x => x != null && typeof x.concat === 'function'
1067 | );
1068 |
1069 | // concat :: Semigroup a => a -> a -> a
1070 | const concat =
1071 | def ('concat')
1072 | ({a: [Semigroup]})
1073 | ([a, a, a])
1074 | (x => y => x.concat (y));
1075 |
1076 | concat ([1, 2]) ([3, 4]);
1077 | // => [1, 2, 3, 4]
1078 |
1079 | concat (null) (null);
1080 | // ! TypeError: Type-class constraint violation
1081 | //
1082 | // concat :: Semigroup a => a -> a -> a
1083 | // ^^^^^^^^^^^ ^
1084 | // 1
1085 | //
1086 | // 1) null :: Null
1087 | //
1088 | // ‘concat’ requires ‘a’ to satisfy the Semigroup type-class constraint; the value at position 1 does not.
1089 | //
1090 | // See http://example.com/my-package#Semigroup for information about the my-package/Semigroup type class.
1091 | ```
1092 |
1093 | Multiple constraints may be placed on a type variable by including
1094 | multiple `TypeClass` values in the array (e.g. `{a: [Foo, Bar, Baz]}`).
1095 |
1096 | [Buffer]: https://nodejs.org/api/buffer.html#buffer_buffer
1097 | [Descending]: https://github.com/sanctuary-js/sanctuary-descending/tree/v2.1.0
1098 | [Either]: https://github.com/sanctuary-js/sanctuary-either/tree/v2.1.0
1099 | [FL:Semigroup]: https://github.com/fantasyland/fantasy-land#semigroup
1100 | [HTML element]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element
1101 | [Identity]: https://github.com/sanctuary-js/sanctuary-identity/tree/v2.1.0
1102 | [Maybe]: https://github.com/sanctuary-js/sanctuary-maybe/tree/v2.1.0
1103 | [Monoid]: https://github.com/fantasyland/fantasy-land#monoid
1104 | [Pair]: https://github.com/sanctuary-js/sanctuary-pair/tree/v2.1.0
1105 | [Setoid]: https://github.com/fantasyland/fantasy-land#setoid
1106 | [Unknown]: #Unknown
1107 | [`Array`]: #Array
1108 | [`Array2`]: #Array2
1109 | [`BinaryType`]: #BinaryType
1110 | [`Date`]: #Date
1111 | [`FiniteNumber`]: #FiniteNumber
1112 | [`GlobalRegExp`]: #GlobalRegExp
1113 | [`Integer`]: #Integer
1114 | [`NonGlobalRegExp`]: #NonGlobalRegExp
1115 | [`Number`]: #Number
1116 | [`Object.create`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
1117 | [`RegExp`]: #RegExp
1118 | [`RegexFlags`]: #RegexFlags
1119 | [`String`]: #String
1120 | [`SyntaxError`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError
1121 | [`TypeClass`]: https://github.com/sanctuary-js/sanctuary-type-classes#TypeClass
1122 | [`TypeError`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError
1123 | [`TypeVariable`]: #TypeVariable
1124 | [`UnaryType`]: #UnaryType
1125 | [`UnaryTypeVariable`]: #UnaryTypeVariable
1126 | [`Unknown`]: #Unknown
1127 | [`ValidNumber`]: #ValidNumber
1128 | [`env`]: #env
1129 | [arguments]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments
1130 | [enumerated types]: https://en.wikipedia.org/wiki/Enumerated_type
1131 | [max]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER
1132 | [min]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_SAFE_INTEGER
1133 | [semigroup]: https://en.wikipedia.org/wiki/Semigroup
1134 | [type class]: #type-classes
1135 | [type variables]: #TypeVariable
1136 | [types]: #types
1137 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /* ___ ______
2 | / /\ / ___/\
3 | ______/ / / _______ __/ /___\/
4 | / ___ / / / ___ \ /_ __/\
5 | / /\_/ / / / /__/ /\ \/ /\_\/
6 | / / // / / / ______/ / / / /
7 | / /_// / / / /______\/ / / /
8 | \_______/ / \_______/\ /__/ /
9 | \______\/ \______\/ \__*/
10 |
11 | //. # sanctuary-def
12 | //.
13 | //. sanctuary-def is a run-time type system for JavaScript. It facilitates
14 | //. the definition of curried JavaScript functions that are explicit about
15 | //. the number of arguments to which they may be applied and the types of
16 | //. those arguments.
17 | //.
18 | //. It is conventional to import the package as `$`:
19 | //.
20 | //. ```javascript
21 | //. const $ = require ('sanctuary-def');
22 | //. ```
23 | //.
24 | //. [`def`][] is a function for defining functions. For example:
25 | //.
26 | //. ```javascript
27 | //. // add :: Number -> Number -> Number
28 | //. const add =
29 | //. $.def ('add') // name
30 | //. ({}) // type-class constraints
31 | //. ([$.Number, $.Number, $.Number]) // input and output types
32 | //. (x => y => x + y); // implementation
33 | //. ```
34 | //.
35 | //. `[$.Number, $.Number, $.Number]` specifies that `add` takes two arguments
36 | //. of type `Number`, one at a time, and returns a value of type `Number`.
37 | //.
38 | //. Applying `add` to two arguments, one at a time, gives the expected result:
39 | //.
40 | //. ```javascript
41 | //. add (2) (2);
42 | //. // => 4
43 | //. ```
44 | //.
45 | //. Applying `add` to multiple arguments at once results in an exception being
46 | //. thrown:
47 | //.
48 | //. ```javascript
49 | //. add (2, 2, 2);
50 | //. // ! TypeError: ‘add’ applied to the wrong number of arguments
51 | //. //
52 | //. // add :: Number -> Number -> Number
53 | //. // ^^^^^^
54 | //. // 1
55 | //. //
56 | //. // Expected one argument but received three arguments:
57 | //. //
58 | //. // - 2
59 | //. // - 2
60 | //. // - 2
61 | //. ```
62 | //.
63 | //. Applying `add` to one argument produces a function awaiting the remaining
64 | //. argument. This is known as partial application. Partial application allows
65 | //. more specific functions to be defined in terms of more general ones:
66 | //.
67 | //. ```javascript
68 | //. // inc :: Number -> Number
69 | //. const inc = add (1);
70 | //.
71 | //. inc (7);
72 | //. // => 8
73 | //. ```
74 | //.
75 | //. JavaScript's implicit type coercion often obfuscates the source of type
76 | //. errors. Consider the following function:
77 | //.
78 | //. ```javascript
79 | //. // _add :: Number -> Number -> Number
80 | //. const _add = x => y => x + y;
81 | //. ```
82 | //.
83 | //. The type signature indicates that `_add` takes arguments of type `Number`,
84 | //. but this is not enforced. This allows type errors to be silently ignored:
85 | //.
86 | //. ```javascript
87 | //. _add ('2') ('2');
88 | //. // => '22'
89 | //. ```
90 | //.
91 | //. `add`, on the other hand, throws if applied to arguments of the wrong
92 | //. types:
93 | //.
94 | //. ```javascript
95 | //. add ('2') ('2');
96 | //. // ! TypeError: Invalid value
97 | //. //
98 | //. // add :: Number -> Number -> Number
99 | //. // ^^^^^^
100 | //. // 1
101 | //. //
102 | //. // 1) "2" :: String
103 | //. //
104 | //. // The value at position 1 is not a member of ‘Number’.
105 | //. ```
106 | //.
107 | //. Type checking is performed as arguments are provided (rather than once all
108 | //. arguments have been provided), so type errors are reported early:
109 | //.
110 | //. ```javascript
111 | //. add ('X');
112 | //. // ! TypeError: Invalid value
113 | //. //
114 | //. // add :: Number -> Number -> Number
115 | //. // ^^^^^^
116 | //. // 1
117 | //. //
118 | //. // 1) "X" :: String
119 | //. //
120 | //. // The value at position 1 is not a member of ‘Number’.
121 | //. ```
122 | //.
123 | //. `add` is monomorphic: its input types and output type are fixed. Some
124 | //. functions are polymorphic: they can operate on values of any type.
125 | //.
126 | //. The identity function is the simplest polymorphic function:
127 | //.
128 | //. ```javascript
129 | //. // id :: a -> a
130 | //. const id = x => x;
131 | //. ```
132 | //.
133 | //. One would need a [type variable][] to define the identity function.
134 | //. Type variables are refined at run-time as input and output values are
135 | //. observed during an application of a function. This works by initially
136 | //. associating the set of all possible types with each of the function's
137 | //. distinct type variables. Each time a value is observed in the position
138 | //. of a type variable, the associated set of types is filtered: each type
139 | //. that does not include the value as a member is removed from the set.
140 | //.
141 | //. This raises the question of what is the set of all possible types,
142 | //. known henceforth as the environment. Although the environment is a
143 | //. set conceptually, it is represented as an array, [`config.env`][],
144 | //. which initially comprises all the built-in JavaScript [types][].
145 | //.
146 | //. One is free to define custom types and add these to the environment:
147 | //.
148 | //. ```javascript
149 | //. $.config.env.push (Foo, Bar);
150 | //. ```
151 | //.
152 | //. This would make members of the `Foo` and `Bar` types compatible with
153 | //. polymorphic functions.
154 | //.
155 | //. Type constructors such as `List :: Type -> Type` cannot be included in
156 | //. the environment as they're not of the correct type. One could, though,
157 | //. use a type constructor to define a fixed number of concrete types:
158 | //.
159 | //. ```javascript
160 | //. $.config.env.push (
161 | //. List ($.Number), // :: Type
162 | //. List ($.String), // :: Type
163 | //. List (List ($.Number)), // :: Type
164 | //. List (List ($.String)), // :: Type
165 | //. List (List (List ($.Number))), // :: Type
166 | //. List (List (List ($.String))), // :: Type
167 | //. );
168 | //. ```
169 | //.
170 | //. Not only would this be tedious, but one could never enumerate all possible
171 | //. types as there are infinitely many. Instead, one should use [`Unknown`][]:
172 | //.
173 | //. ```javascript
174 | //. $.config.env.push (List ($.Unknown));
175 | //. ```
176 |
177 | import E from 'sanctuary-either';
178 | import show from 'sanctuary-show';
179 | import Z from 'sanctuary-type-classes';
180 | import type from 'sanctuary-type-identifiers';
181 |
182 | const {hasOwnProperty, toString} = globalThis.Object.prototype;
183 |
184 | const {Left, Right} = E;
185 |
186 | // complement :: (a -> Boolean) -> a -> Boolean
187 | const complement = pred => x => !(pred (x));
188 |
189 | // isPrefix :: Array a -> Array a -> Boolean
190 | const isPrefix = candidate => xs => {
191 | if (candidate.length > xs.length) return false;
192 | for (let idx = 0; idx < candidate.length; idx += 1) {
193 | if (candidate[idx] !== xs[idx]) return false;
194 | }
195 | return true;
196 | };
197 |
198 | // toArray :: Foldable f => f a -> Array a
199 | const toArray = foldable => (
200 | globalThis.Array.isArray (foldable)
201 | ? foldable
202 | : Z.reduce ((xs, x) => ((xs.push (x), xs)), [], foldable)
203 | );
204 |
205 | // stripNamespace :: TypeClass -> String
206 | const stripNamespace = ({name}) => name.slice (name.indexOf ('/') + 1);
207 |
208 | const _test = x => function recur(t) {
209 | return t.supertypes.every (recur) && t.test (x);
210 | };
211 |
212 | const Type$prototype = {
213 | '@@type': 'sanctuary-def/Type@1',
214 | '@@show': function() {
215 | return this.format (s => s, k => s => s);
216 | },
217 | 'validate': function(x) {
218 | if (!(_test (x) (this))) return Left ({value: x, propPath: []});
219 | for (let idx = 0; idx < this.keys.length; idx += 1) {
220 | const k = this.keys[idx];
221 | const t = this.types[k];
222 | const ys = this.extractors[k] (x);
223 | for (let idx2 = 0; idx2 < ys.length; idx2 += 1) {
224 | const result = t.validate (ys[idx2]);
225 | if (result.isLeft) {
226 | return Left ({value: result.value.value,
227 | propPath: [k, ...result.value.propPath]});
228 | }
229 | }
230 | }
231 | return Right (x);
232 | },
233 | 'fantasy-land/equals': function(other) {
234 | return (
235 | Z.equals (this.type, other.type) &&
236 | Z.equals (this.name, other.name) &&
237 | Z.equals (this.url, other.url) &&
238 | Z.equals (this.supertypes, other.supertypes) &&
239 | this.keys.length === other.keys.length &&
240 | this.keys.every (k => other.keys.includes (k)) &&
241 | Z.equals (this.types, other.types)
242 | );
243 | },
244 | };
245 |
246 | // _Type :: ... -> Type
247 | const _Type = (
248 | type, // :: String
249 | name, // :: String
250 | url, // :: String
251 | arity, // :: NonNegativeInteger
252 | format,
253 | // :: Nullable ((String -> String, String -> String -> String) -> String)
254 | supertypes, // :: Array Type
255 | test, // :: Any -> Boolean
256 | tuples // :: Array (Array3 String (a -> Array b) Type)
257 | ) => globalThis.Object.assign (
258 | globalThis.Object.create (Type$prototype, {
259 | _extractors: {
260 | value: tuples.reduce ((extractors, [k, e]) => ((
261 | extractors[k] = e,
262 | extractors
263 | )), {}),
264 | },
265 | extractors: {
266 | value: tuples.reduce ((extractors, [k, e]) => ((
267 | extractors[k] = x => toArray (e (x)),
268 | extractors
269 | )), {}),
270 | },
271 | format: {
272 | value: format || ((outer, inner) =>
273 | outer (name) +
274 | Z.foldMap (
275 | globalThis.String,
276 | ([k, , t]) => (
277 | t.arity > 0
278 | ? outer (' ') + outer ('(') + inner (k) (show (t)) + outer (')')
279 | : outer (' ') + inner (k) (show (t))
280 | ),
281 | tuples
282 | )
283 | ),
284 | },
285 | test: {
286 | value: test,
287 | },
288 | }),
289 | {
290 | arity, // number of type parameters
291 | keys: tuples.map (([k]) => k),
292 | name,
293 | supertypes,
294 | type,
295 | types: tuples.reduce ((types, [k, , t]) => ((types[k] = t, types)), {}),
296 | url,
297 | }
298 | );
299 |
300 | const BINARY = 'BINARY';
301 | const FUNCTION = 'FUNCTION';
302 | const INCONSISTENT = 'INCONSISTENT';
303 | const NO_ARGUMENTS = 'NO_ARGUMENTS';
304 | const NULLARY = 'NULLARY';
305 | const RECORD = 'RECORD';
306 | const UNARY = 'UNARY';
307 | const UNKNOWN = 'UNKNOWN';
308 | const VARIABLE = 'VARIABLE';
309 |
310 | // Inconsistent :: Type
311 | const Inconsistent = _Type (
312 | INCONSISTENT,
313 | '',
314 | '',
315 | 0,
316 | (outer, inner) => '???',
317 | [],
318 | null,
319 | []
320 | );
321 |
322 | // NoArguments :: Type
323 | const NoArguments = _Type (
324 | NO_ARGUMENTS,
325 | '',
326 | '',
327 | 0,
328 | (outer, inner) => '()',
329 | [],
330 | null,
331 | []
332 | );
333 |
334 | // functionUrl :: String -> String
335 | const functionUrl = name => {
336 | const version = '0.22.0'; // updated programmatically
337 | return (
338 | `https://github.com/sanctuary-js/sanctuary-def/tree/v${version}#${name}`
339 | );
340 | };
341 |
342 | const NullaryTypeWithUrl = name => supertypes => test => (
343 | _NullaryType (name) (functionUrl (name)) (supertypes) (test)
344 | );
345 |
346 | const UnaryTypeWithUrl = name => supertypes => test => _1 => (
347 | _def (name)
348 | ({})
349 | ([Type, Type])
350 | (_UnaryType (name) (functionUrl (name)) (supertypes) (test) (_1))
351 | );
352 |
353 | const BinaryTypeWithUrl = name => supertypes => test => _1 => _2 => (
354 | _def (name)
355 | ({})
356 | ([Type, Type, Type])
357 | (_BinaryType (name) (functionUrl (name)) (supertypes) (test) (_1) (_2))
358 | );
359 |
360 | //# def :: String -> StrMap (Array TypeClass) -> Array Type -> Function -> Function
361 | //.
362 | //. Wraps a function to produce an equivalent function that checks the
363 | //. types of its inputs and output each time it is applied.
364 | //.
365 | //. Takes a name, an object specifying type-class constraints, an array
366 | //. of types, and a function. Returns the type-checked equivalent of the
367 | //. given function.
368 | const _def = name => constraints => types => impl => {
369 | const typeInfo = {
370 | name,
371 | constraints,
372 | types: types.length === 1 ? [NoArguments, ...types] : types,
373 | };
374 | return withTypeChecking (typeInfo, impl);
375 | };
376 |
377 | export const config = ((
378 | $checkTypes = true,
379 | $env = [],
380 | ) => ({
381 | get checkTypes() {
382 | return $checkTypes;
383 | },
384 | set checkTypes(checkTypes) {
385 | if (typeof checkTypes !== 'boolean') {
386 | throw new TypeError (
387 | "Value of 'checkTypes' property must be either true or false"
388 | );
389 | }
390 | $checkTypes = checkTypes;
391 | },
392 | get env() {
393 | return $env;
394 | },
395 | })) ();
396 |
397 | //. ### Types
398 | //.
399 | //. Conceptually, a type is a set of values. One can think of a value of
400 | //. type `Type` as a function of type `Any -> Boolean` that tests values
401 | //. for membership in the set (though this is an oversimplification).
402 |
403 | //# Type :: Type
404 | //.
405 | //. Type comprising every `Type` value.
406 | export const Type = NullaryTypeWithUrl
407 | ('Type')
408 | ([])
409 | (x => type (x) === 'sanctuary-def/Type@1');
410 |
411 | //# NonEmpty :: Type -> Type
412 | //.
413 | //. Constructor for non-empty types. `$.NonEmpty ($.String)`, for example, is
414 | //. the type comprising every [`String`][] value except `''`.
415 | //.
416 | //. The given type must satisfy the [Monoid][] and [Setoid][] specifications.
417 | export const NonEmpty = UnaryTypeWithUrl
418 | ('NonEmpty')
419 | ([])
420 | (x => Z.Monoid.test (x) &&
421 | Z.Setoid.test (x) &&
422 | !(Z.equals (x, Z.empty (x.constructor))))
423 | (monoid => [monoid]);
424 |
425 | //# Unknown :: Type
426 | //.
427 | //. Type used to represent missing type information. The type of `[]`,
428 | //. for example, is `Array ???`.
429 | //.
430 | //. May be used with type constructors when defining environments. Given a
431 | //. type constructor `List :: Type -> Type`, one could use `List ($.Unknown)`
432 | //. to include an infinite number of types in an environment:
433 | //.
434 | //. - `List Number`
435 | //. - `List String`
436 | //. - `List (List Number)`
437 | //. - `List (List String)`
438 | //. - `List (List (List Number))`
439 | //. - `List (List (List String))`
440 | //. - `...`
441 | export const Unknown = _Type (
442 | UNKNOWN,
443 | '',
444 | '',
445 | 0,
446 | (outer, inner) => 'Unknown',
447 | [],
448 | x => true,
449 | []
450 | );
451 |
452 | //# Void :: Type
453 | //.
454 | //. Uninhabited type.
455 | //.
456 | //. May be used to convey that a type parameter of an algebraic data type
457 | //. will not be used. For example, a future of type `Future Void String`
458 | //. will never be rejected.
459 | export const Void = NullaryTypeWithUrl
460 | ('Void')
461 | ([])
462 | (x => false);
463 |
464 | //# Any :: Type
465 | //.
466 | //. Type comprising every JavaScript value.
467 | export const Any = NullaryTypeWithUrl
468 | ('Any')
469 | ([])
470 | (x => true);
471 |
472 | //# AnyFunction :: Type
473 | //.
474 | //. Type comprising every Function value.
475 | export const AnyFunction = NullaryTypeWithUrl
476 | ('Function')
477 | ([])
478 | (x => typeof x === 'function');
479 |
480 | //# Arguments :: Type
481 | //.
482 | //. Type comprising every [`arguments`][arguments] object.
483 | export const Arguments = NullaryTypeWithUrl
484 | ('Arguments')
485 | ([])
486 | (x => type (x) === 'Arguments');
487 |
488 | //# Array :: Type -> Type
489 | //.
490 | //. Constructor for homogeneous Array types.
491 | export const Array = UnaryTypeWithUrl
492 | ('Array')
493 | ([])
494 | (x => type (x) === 'Array')
495 | (array => array);
496 |
497 | //# Array0 :: Type
498 | //.
499 | //. Type whose sole member is `[]`.
500 | export const Array0 = NullaryTypeWithUrl
501 | ('Array0')
502 | ([Array (Unknown)])
503 | (array => array.length === 0);
504 |
505 | //# Array1 :: Type -> Type
506 | //.
507 | //. Constructor for singleton Array types.
508 | export const Array1 = UnaryTypeWithUrl
509 | ('Array1')
510 | ([Array (Unknown)])
511 | (array => array.length === 1)
512 | (array1 => array1);
513 |
514 | //# Array2 :: Type -> Type -> Type
515 | //.
516 | //. Constructor for heterogeneous Array types of length 2. `['foo', true]` is
517 | //. a member of `Array2 String Boolean`.
518 | export const Array2 = BinaryTypeWithUrl
519 | ('Array2')
520 | ([Array (Unknown)])
521 | (array => array.length === 2)
522 | (array2 => [array2[0]])
523 | (array2 => [array2[1]]);
524 |
525 | //# Boolean :: Type
526 | //.
527 | //. Type comprising `true` and `false`.
528 | export const Boolean = NullaryTypeWithUrl
529 | ('Boolean')
530 | ([])
531 | (x => typeof x === 'boolean');
532 |
533 | //# Buffer :: Type
534 | //.
535 | //. Type comprising every [Buffer][] object.
536 | export const Buffer = NullaryTypeWithUrl
537 | ('Buffer')
538 | ([])
539 | (x => globalThis.Buffer != null && globalThis.Buffer.isBuffer (x));
540 |
541 | //# Date :: Type
542 | //.
543 | //. Type comprising every Date value.
544 | export const Date = NullaryTypeWithUrl
545 | ('Date')
546 | ([])
547 | (x => type (x) === 'Date');
548 |
549 | //# ValidDate :: Type
550 | //.
551 | //. Type comprising every [`Date`][] value except `new Date (NaN)`.
552 | export const ValidDate = NullaryTypeWithUrl
553 | ('ValidDate')
554 | ([Date])
555 | (date => !(globalThis.Number.isNaN (date.valueOf ())));
556 |
557 | //# Descending :: Type -> Type
558 | //.
559 | //. [Descending][] type constructor.
560 | export const Descending = UnaryTypeWithUrl
561 | ('Descending')
562 | ([])
563 | (x => type (x) === 'sanctuary-descending/Descending@1')
564 | (descending => descending);
565 |
566 | //# Either :: Type -> Type -> Type
567 | //.
568 | //. [Either][] type constructor.
569 | export const Either = BinaryTypeWithUrl
570 | ('Either')
571 | ([])
572 | (x => type (x) === 'sanctuary-either/Either@1')
573 | (either => either.isLeft ? [either.value] : [])
574 | (either => either.isLeft ? [] : [either.value]);
575 |
576 | //# Error :: Type
577 | //.
578 | //. Type comprising every Error value, including values of more specific
579 | //. constructors such as [`SyntaxError`][] and [`TypeError`][].
580 | export const Error = NullaryTypeWithUrl
581 | ('Error')
582 | ([])
583 | (x => type (x) === 'Error');
584 |
585 | //# Fn :: Type -> Type -> Type
586 | //.
587 | //. Binary type constructor for unary function types. `$.Fn (I) (O)`
588 | //. represents `I -> O`, the type of functions that take a value of
589 | //. type `I` and return a value of type `O`.
590 | export const Fn = _def
591 | ('Fn')
592 | ({})
593 | ([Type, Type, Type])
594 | ($1 => $2 => Function ([$1, $2]));
595 |
596 | //# Function :: NonEmpty (Array Type) -> Type
597 | //.
598 | //. Constructor for Function types.
599 | //.
600 | //. Examples:
601 | //.
602 | //. - `$.Function ([$.Date, $.String])` represents the `Date -> String`
603 | //. type; and
604 | //. - `$.Function ([a, b, a])` represents the `(a, b) -> a` type.
605 | export const Function = _def
606 | ('Function')
607 | ({})
608 | ([NonEmpty (Array (Type)), Type])
609 | (types =>
610 | _Type (
611 | FUNCTION,
612 | '',
613 | '',
614 | types.length,
615 | (outer, inner) => {
616 | const repr = (
617 | types
618 | .slice (0, -1)
619 | .map ((t, idx) =>
620 | t.type === FUNCTION
621 | ? outer ('(') + inner (`$${idx + 1}`) (show (t)) + outer (')')
622 | : inner (`$${idx + 1}`) (show (t))
623 | )
624 | .join (outer (', '))
625 | );
626 | return (
627 | (types.length === 2 ? repr : outer ('(') + repr + outer (')')) +
628 | outer (' -> ') +
629 | inner (`$${types.length}`)
630 | (show (types[types.length - 1]))
631 | );
632 | },
633 | [AnyFunction],
634 | x => true,
635 | types.map ((t, idx) => [`$${idx + 1}`, x => [], t])
636 | ));
637 |
638 | //# HtmlElement :: Type
639 | //.
640 | //. Type comprising every [HTML element][].
641 | export const HtmlElement = NullaryTypeWithUrl
642 | ('HtmlElement')
643 | ([])
644 | (x => /^\[object HTML.*Element\]$/.test (toString.call (x)));
645 |
646 | //# Identity :: Type -> Type
647 | //.
648 | //. [Identity][] type constructor.
649 | export const Identity = UnaryTypeWithUrl
650 | ('Identity')
651 | ([])
652 | (x => type (x) === 'sanctuary-identity/Identity@1')
653 | (identity => identity);
654 |
655 | //# JsMap :: Type -> Type -> Type
656 | //.
657 | //. Constructor for native Map types. `$.JsMap ($.Number) ($.String)`,
658 | //. for example, is the type comprising every native Map whose keys are
659 | //. numbers and whose values are strings.
660 | export const JsMap = BinaryTypeWithUrl
661 | ('JsMap')
662 | ([])
663 | (x => toString.call (x) === '[object Map]')
664 | (jsMap => globalThis.Array.from (jsMap.keys ()))
665 | (jsMap => globalThis.Array.from (jsMap.values ()));
666 |
667 | //# JsSet :: Type -> Type
668 | //.
669 | //. Constructor for native Set types. `$.JsSet ($.Number)`, for example,
670 | //. is the type comprising every native Set whose values are numbers.
671 | export const JsSet = UnaryTypeWithUrl
672 | ('JsSet')
673 | ([])
674 | (x => toString.call (x) === '[object Set]')
675 | (jsSet => globalThis.Array.from (jsSet.values ()));
676 |
677 | //# Maybe :: Type -> Type
678 | //.
679 | //. [Maybe][] type constructor.
680 | export const Maybe = UnaryTypeWithUrl
681 | ('Maybe')
682 | ([])
683 | (x => type (x) === 'sanctuary-maybe/Maybe@1')
684 | (maybe => maybe);
685 |
686 | //# Module :: Type
687 | //.
688 | //. Type comprising every ES module.
689 | export const Module = NullaryTypeWithUrl
690 | ('Module')
691 | ([])
692 | (x => toString.call (x) === '[object Module]');
693 |
694 | //# Null :: Type
695 | //.
696 | //. Type whose sole member is `null`.
697 | export const Null = NullaryTypeWithUrl
698 | ('Null')
699 | ([])
700 | (x => type (x) === 'Null');
701 |
702 | //# Nullable :: Type -> Type
703 | //.
704 | //. Constructor for types that include `null` as a member.
705 | export const Nullable = UnaryTypeWithUrl
706 | ('Nullable')
707 | ([])
708 | (x => true)
709 | // eslint-disable-next-line eqeqeq
710 | (nullable => nullable === null ? [] : [nullable]);
711 |
712 | //# Number :: Type
713 | //.
714 | //. Type comprising every primitive Number value (including `NaN`).
715 | export const Number = NullaryTypeWithUrl
716 | ('Number')
717 | ([])
718 | (x => typeof x === 'number');
719 |
720 | const nonZero = x => x !== 0;
721 | const nonNegative = x => x >= 0;
722 | const positive = x => x > 0;
723 | const negative = x => x < 0;
724 |
725 | //# PositiveNumber :: Type
726 | //.
727 | //. Type comprising every [`Number`][] value greater than zero.
728 | export const PositiveNumber = NullaryTypeWithUrl
729 | ('PositiveNumber')
730 | ([Number])
731 | (positive);
732 |
733 | //# NegativeNumber :: Type
734 | //.
735 | //. Type comprising every [`Number`][] value less than zero.
736 | export const NegativeNumber = NullaryTypeWithUrl
737 | ('NegativeNumber')
738 | ([Number])
739 | (negative);
740 |
741 | //# ValidNumber :: Type
742 | //.
743 | //. Type comprising every [`Number`][] value except `NaN`.
744 | export const ValidNumber = NullaryTypeWithUrl
745 | ('ValidNumber')
746 | ([Number])
747 | (complement (globalThis.Number.isNaN));
748 |
749 | //# NonZeroValidNumber :: Type
750 | //.
751 | //. Type comprising every [`ValidNumber`][] value except `0` and `-0`.
752 | export const NonZeroValidNumber = NullaryTypeWithUrl
753 | ('NonZeroValidNumber')
754 | ([ValidNumber])
755 | (nonZero);
756 |
757 | //# FiniteNumber :: Type
758 | //.
759 | //. Type comprising every [`ValidNumber`][] value except `Infinity` and
760 | //. `-Infinity`.
761 | export const FiniteNumber = NullaryTypeWithUrl
762 | ('FiniteNumber')
763 | ([ValidNumber])
764 | (isFinite);
765 |
766 | //# NonZeroFiniteNumber :: Type
767 | //.
768 | //. Type comprising every [`FiniteNumber`][] value except `0` and `-0`.
769 | export const NonZeroFiniteNumber = NullaryTypeWithUrl
770 | ('NonZeroFiniteNumber')
771 | ([FiniteNumber])
772 | (nonZero);
773 |
774 | //# PositiveFiniteNumber :: Type
775 | //.
776 | //. Type comprising every [`FiniteNumber`][] value greater than zero.
777 | export const PositiveFiniteNumber = NullaryTypeWithUrl
778 | ('PositiveFiniteNumber')
779 | ([FiniteNumber])
780 | (positive);
781 |
782 | //# NegativeFiniteNumber :: Type
783 | //.
784 | //. Type comprising every [`FiniteNumber`][] value less than zero.
785 | export const NegativeFiniteNumber = NullaryTypeWithUrl
786 | ('NegativeFiniteNumber')
787 | ([FiniteNumber])
788 | (negative);
789 |
790 | //# Integer :: Type
791 | //.
792 | //. Type comprising every integer in the range
793 | //. [[`Number.MIN_SAFE_INTEGER`][min] .. [`Number.MAX_SAFE_INTEGER`][max]].
794 | export const Integer = NullaryTypeWithUrl
795 | ('Integer')
796 | ([ValidNumber])
797 | (x => Math.floor (x) === x &&
798 | x >= globalThis.Number.MIN_SAFE_INTEGER &&
799 | x <= globalThis.Number.MAX_SAFE_INTEGER);
800 |
801 | //# NonZeroInteger :: Type
802 | //.
803 | //. Type comprising every [`Integer`][] value except `0` and `-0`.
804 | export const NonZeroInteger = NullaryTypeWithUrl
805 | ('NonZeroInteger')
806 | ([Integer])
807 | (nonZero);
808 |
809 | //# NonNegativeInteger :: Type
810 | //.
811 | //. Type comprising every non-negative [`Integer`][] value (including `-0`).
812 | //. Also known as the set of natural numbers under ISO 80000-2:2009.
813 | export const NonNegativeInteger = NullaryTypeWithUrl
814 | ('NonNegativeInteger')
815 | ([Integer])
816 | (nonNegative);
817 |
818 | //# PositiveInteger :: Type
819 | //.
820 | //. Type comprising every [`Integer`][] value greater than zero.
821 | export const PositiveInteger = NullaryTypeWithUrl
822 | ('PositiveInteger')
823 | ([Integer])
824 | (positive);
825 |
826 | //# NegativeInteger :: Type
827 | //.
828 | //. Type comprising every [`Integer`][] value less than zero.
829 | export const NegativeInteger = NullaryTypeWithUrl
830 | ('NegativeInteger')
831 | ([Integer])
832 | (negative);
833 |
834 | //# Object :: Type
835 | //.
836 | //. Type comprising every "plain" Object value. Specifically, values
837 | //. created via:
838 | //.
839 | //. - object literal syntax;
840 | //. - [`Object.create`][]; or
841 | //. - the `new` operator in conjunction with `Object` or a custom
842 | //. constructor function.
843 | export const Object = NullaryTypeWithUrl
844 | ('Object')
845 | ([])
846 | (x => type (x) === 'Object');
847 |
848 | //# Pair :: Type -> Type -> Type
849 | //.
850 | //. [Pair][] type constructor.
851 | export const Pair = BinaryTypeWithUrl
852 | ('Pair')
853 | ([])
854 | (x => type (x) === 'sanctuary-pair/Pair@1')
855 | (pair => [pair.fst])
856 | (pair => [pair.snd]);
857 |
858 | //# RegExp :: Type
859 | //.
860 | //. Type comprising every RegExp value.
861 | export const RegExp = NullaryTypeWithUrl
862 | ('RegExp')
863 | ([])
864 | (x => type (x) === 'RegExp');
865 |
866 | //# GlobalRegExp :: Type
867 | //.
868 | //. Type comprising every [`RegExp`][] value whose `global` flag is `true`.
869 | //.
870 | //. See also [`NonGlobalRegExp`][].
871 | export const GlobalRegExp = NullaryTypeWithUrl
872 | ('GlobalRegExp')
873 | ([RegExp])
874 | (regexp => regexp.global);
875 |
876 | //# NonGlobalRegExp :: Type
877 | //.
878 | //. Type comprising every [`RegExp`][] value whose `global` flag is `false`.
879 | //.
880 | //. See also [`GlobalRegExp`][].
881 | export const NonGlobalRegExp = NullaryTypeWithUrl
882 | ('NonGlobalRegExp')
883 | ([RegExp])
884 | (regexp => !regexp.global);
885 |
886 | //# StrMap :: Type -> Type
887 | //.
888 | //. Constructor for homogeneous Object types.
889 | //.
890 | //. `{foo: 1, bar: 2, baz: 3}`, for example, is a member of `StrMap Number`;
891 | //. `{foo: 1, bar: 2, baz: 'XXX'}` is not.
892 | export const StrMap = UnaryTypeWithUrl
893 | ('StrMap')
894 | ([Object])
895 | (x => true)
896 | (strMap => strMap);
897 |
898 | //# String :: Type
899 | //.
900 | //. Type comprising every primitive String value.
901 | export const String = NullaryTypeWithUrl
902 | ('String')
903 | ([])
904 | (x => typeof x === 'string');
905 |
906 | //# RegexFlags :: Type
907 | //.
908 | //. Type comprising the RegExp flags *accepted by the runtime*.
909 | //. Repeated characters are not permitted. Invalid combinations
910 | //. (such as `'uv'`) are not permitted.
911 | export const RegexFlags = NullaryTypeWithUrl
912 | ('RegexFlags')
913 | ([String])
914 | (s => {
915 | try { globalThis.RegExp ('', s); } catch { return false; }
916 | return true;
917 | });
918 |
919 | //# Symbol :: Type
920 | //.
921 | //. Type comprising every Symbol value.
922 | export const Symbol = NullaryTypeWithUrl
923 | ('Symbol')
924 | ([])
925 | (x => typeof x === 'symbol');
926 |
927 | //# TypeClass :: Type
928 | //.
929 | //. Type comprising every [`TypeClass`][] value.
930 | export const TypeClass = NullaryTypeWithUrl
931 | ('TypeClass')
932 | ([])
933 | (x => type (x) === 'sanctuary-type-classes/TypeClass@1');
934 |
935 | //# Undefined :: Type
936 | //.
937 | //. Type whose sole member is `undefined`.
938 | export const Undefined = NullaryTypeWithUrl
939 | ('Undefined')
940 | ([])
941 | (x => type (x) === 'Undefined');
942 |
943 | //# config.checkTypes :: Boolean
944 | //.
945 | //. Run-time type checking is not free; one may wish to pay the performance
946 | //. cost during development but not in production. Setting `config.checkTypes`
947 | //. to `false` disables type checking, even for functions already defined.
948 | //.
949 | //. One may choose to use an environment variable to control type checking:
950 | //.
951 | //. ```javascript
952 | //. $.config.checkTypes = process.env.NODE_ENV === 'development';
953 | //. ```
954 |
955 | //# config.env :: Array Type
956 | //.
957 | //. An array of [types][]:
958 | //.
959 | //. - [AnyFunction](#AnyFunction)
960 | //. - [Arguments](#Arguments)
961 | //. - [Array](#Array) ([Unknown][])
962 | //. - [Array2](#Array2) ([Unknown][]) ([Unknown][])
963 | //. - [Boolean](#Boolean)
964 | //. - [Buffer](#Buffer)
965 | //. - [Date](#Date)
966 | //. - [Descending](#Descending) ([Unknown][])
967 | //. - [Either](#Either) ([Unknown][]) ([Unknown][])
968 | //. - [Error](#Error)
969 | //. - [Fn](#Fn) ([Unknown][]) ([Unknown][])
970 | //. - [HtmlElement](#HtmlElement)
971 | //. - [Identity](#Identity) ([Unknown][])
972 | //. - [JsMap](#JsMap) ([Unknown][]) ([Unknown][])
973 | //. - [JsSet](#JsSet) ([Unknown][])
974 | //. - [Maybe](#Maybe) ([Unknown][])
975 | //. - [Module](#Module)
976 | //. - [Null](#Null)
977 | //. - [Number](#Number)
978 | //. - [Object](#Object)
979 | //. - [Pair](#Pair) ([Unknown][]) ([Unknown][])
980 | //. - [RegExp](#RegExp)
981 | //. - [StrMap](#StrMap) ([Unknown][])
982 | //. - [String](#String)
983 | //. - [Symbol](#Symbol)
984 | //. - [Type](#Type)
985 | //. - [TypeClass](#TypeClass)
986 | //. - [Undefined](#Undefined)
987 | config.env.push (
988 | AnyFunction,
989 | Arguments,
990 | Array (Unknown),
991 | Array2 (Unknown) (Unknown),
992 | Boolean,
993 | Buffer,
994 | Date,
995 | Descending (Unknown),
996 | Either (Unknown) (Unknown),
997 | Error,
998 | Fn (Unknown) (Unknown),
999 | HtmlElement,
1000 | Identity (Unknown),
1001 | JsMap (Unknown) (Unknown),
1002 | JsSet (Unknown),
1003 | Maybe (Unknown),
1004 | Module,
1005 | Null,
1006 | Number,
1007 | Object,
1008 | Pair (Unknown) (Unknown),
1009 | RegExp,
1010 | StrMap (Unknown),
1011 | String,
1012 | Symbol,
1013 | Type,
1014 | TypeClass,
1015 | Undefined,
1016 | );
1017 |
1018 | // Unchecked :: String -> Type
1019 | const Unchecked = s => _NullaryType (s) ('') ([]) (x => true);
1020 |
1021 | // numbers :: Array String
1022 | const numbers = [
1023 | 'zero',
1024 | 'one',
1025 | 'two',
1026 | 'three',
1027 | 'four',
1028 | 'five',
1029 | 'six',
1030 | 'seven',
1031 | 'eight',
1032 | 'nine',
1033 | ];
1034 |
1035 | // numArgs :: Integer -> String
1036 | const numArgs = n => `${
1037 | n < numbers.length ? numbers[n] : show (n)
1038 | } ${
1039 | n === 1 ? 'argument' : 'arguments'
1040 | }`;
1041 |
1042 | // expandUnknown :: (Array Object, Any, (a -> Array b), Type) -> Array Type
1043 | const expandUnknown = (seen, value, extractor, type) => (
1044 | type.type === UNKNOWN
1045 | ? _determineActualTypes (seen, extractor (value))
1046 | : [type]
1047 | );
1048 |
1049 | // _determineActualTypes :: (Array Object, Array Any) -> Array Type
1050 | const _determineActualTypes = (seen, values) => {
1051 | if (values.length === 0) return [Unknown];
1052 |
1053 | const refine = (types, value) => {
1054 | let seen$;
1055 | if (typeof value === 'object' && value != null ||
1056 | typeof value === 'function') {
1057 | // Abort if a circular reference is encountered; add the current
1058 | // object to the array of seen objects otherwise.
1059 | if (seen.indexOf (value) >= 0) return [];
1060 | seen$ = [...seen, value];
1061 | } else {
1062 | seen$ = seen;
1063 | }
1064 | return Z.chain (
1065 | t => (
1066 | (t.validate (value)).isLeft ?
1067 | [] :
1068 | t.type === UNARY ?
1069 | Z.map (
1070 | _UnaryType (t.name)
1071 | (t.url)
1072 | (t.supertypes)
1073 | (t.test)
1074 | (t._extractors.$1),
1075 | expandUnknown (seen$, value, t.extractors.$1, t.types.$1)
1076 | ) :
1077 | t.type === BINARY ?
1078 | Z.lift2 (
1079 | _BinaryType (t.name)
1080 | (t.url)
1081 | (t.supertypes)
1082 | (t.test)
1083 | (t._extractors.$1)
1084 | (t._extractors.$2),
1085 | expandUnknown (seen$, value, t.extractors.$1, t.types.$1),
1086 | expandUnknown (seen$, value, t.extractors.$2, t.types.$2)
1087 | ) :
1088 | // else
1089 | [t]
1090 | ),
1091 | types
1092 | );
1093 | };
1094 | const types = values.reduce (refine, config.env);
1095 | return types.length > 0 ? types : [Inconsistent];
1096 | };
1097 |
1098 | // isConsistent :: Type -> Boolean
1099 | const isConsistent = t => {
1100 | switch (t.type) {
1101 | case INCONSISTENT:
1102 | return false;
1103 | case UNARY:
1104 | return isConsistent (t.types.$1);
1105 | case BINARY:
1106 | return isConsistent (t.types.$1) &&
1107 | isConsistent (t.types.$2);
1108 | default:
1109 | return true;
1110 | }
1111 | };
1112 |
1113 | // determineActualTypesStrict :: Array Any -> Array Type
1114 | const determineActualTypesStrict = values => (
1115 | Z.filter (isConsistent,
1116 | _determineActualTypes ([], values))
1117 | );
1118 |
1119 | // determineActualTypesLoose :: Array Any -> Array Type
1120 | const determineActualTypesLoose = values => (
1121 | Z.reject (t => t.type === INCONSISTENT,
1122 | _determineActualTypes ([], values))
1123 | );
1124 |
1125 | // TypeInfo = { name :: String
1126 | // , constraints :: StrMap (Array TypeClass)
1127 | // , types :: NonEmpty (Array Type) }
1128 | //
1129 | // TypeVarMap = StrMap { types :: Array Type
1130 | // , valuesByPath :: StrMap (Array Any) }
1131 | //
1132 | // PropPath = Array (Number | String)
1133 |
1134 | // updateTypeVarMap :: ... -> TypeVarMap
1135 | const updateTypeVarMap = (
1136 | typeVarMap, // :: TypeVarMap
1137 | typeVar, // :: Type
1138 | index, // :: Integer
1139 | propPath, // :: PropPath
1140 | values // :: Array Any
1141 | ) => {
1142 | const $typeVarMap = {};
1143 | for (const typeVarName in typeVarMap) {
1144 | const entry = typeVarMap[typeVarName];
1145 | const $entry = {types: entry.types.slice (), valuesByPath: {}};
1146 | for (const k in entry.valuesByPath) {
1147 | $entry.valuesByPath[k] = entry.valuesByPath[k].slice ();
1148 | }
1149 | $typeVarMap[typeVarName] = $entry;
1150 | }
1151 | if (!(hasOwnProperty.call ($typeVarMap, typeVar.name))) {
1152 | $typeVarMap[typeVar.name] = {
1153 | types: Z.filter (t => t.arity >= typeVar.arity, config.env),
1154 | valuesByPath: {},
1155 | };
1156 | }
1157 |
1158 | const key = JSON.stringify ([index, ...propPath]);
1159 | if (!(hasOwnProperty.call ($typeVarMap[typeVar.name].valuesByPath, key))) {
1160 | $typeVarMap[typeVar.name].valuesByPath[key] = [];
1161 | }
1162 |
1163 | values.forEach (value => {
1164 | $typeVarMap[typeVar.name].valuesByPath[key].push (value);
1165 | $typeVarMap[typeVar.name].types = Z.chain (
1166 | t => (
1167 | !(test (t) (value)) ?
1168 | [] :
1169 | typeVar.arity === 0 && t.type === UNARY ?
1170 | Z.map (
1171 | _UnaryType (t.name)
1172 | (t.url)
1173 | (t.supertypes)
1174 | (t.test)
1175 | (t._extractors.$1),
1176 | Z.filter (
1177 | isConsistent,
1178 | expandUnknown ([], value, t.extractors.$1, t.types.$1)
1179 | )
1180 | ) :
1181 | typeVar.arity === 0 && t.type === BINARY ?
1182 | Z.lift2 (
1183 | _BinaryType (t.name)
1184 | (t.url)
1185 | (t.supertypes)
1186 | (t.test)
1187 | (t._extractors.$1)
1188 | (t._extractors.$2),
1189 | Z.filter (
1190 | isConsistent,
1191 | expandUnknown ([], value, t.extractors.$1, t.types.$1)
1192 | ),
1193 | Z.filter (
1194 | isConsistent,
1195 | expandUnknown ([], value, t.extractors.$2, t.types.$2)
1196 | )
1197 | ) :
1198 | // else
1199 | [t]
1200 | ),
1201 | $typeVarMap[typeVar.name].types
1202 | );
1203 | });
1204 |
1205 | return $typeVarMap;
1206 | };
1207 |
1208 | // underlineTypeVars :: (TypeInfo, StrMap (Array Any)) -> String
1209 | const underlineTypeVars = (typeInfo, valuesByPath) => {
1210 | // Note: Sorting these keys lexicographically is not "correct", but it
1211 | // does the right thing for indexes less than 10.
1212 | const paths = Z.map (
1213 | JSON.parse,
1214 | Z.sort (globalThis.Object.keys (valuesByPath))
1215 | );
1216 | return (
1217 | underline_ (typeInfo)
1218 | (index => f => t => propPath => s => {
1219 | const indexedPropPath = [index, ...propPath];
1220 | if (paths.some (isPrefix (indexedPropPath))) {
1221 | const key = JSON.stringify (indexedPropPath);
1222 | if (!(hasOwnProperty.call (valuesByPath, key))) return s;
1223 | if (valuesByPath[key].length > 0) return f (s);
1224 | }
1225 | return ' '.repeat (s.length);
1226 | })
1227 | );
1228 | };
1229 |
1230 | // satisfactoryTypes :: ... -> Either (() -> Error)
1231 | // { typeVarMap :: TypeVarMap
1232 | // , types :: Array Type }
1233 | function satisfactoryTypes(
1234 | typeInfo, // :: TypeInfo
1235 | typeVarMap, // :: TypeVarMap
1236 | expType, // :: Type
1237 | index, // :: Integer
1238 | propPath, // :: PropPath
1239 | values // :: Array Any
1240 | ) {
1241 | for (let idx = 0; idx < values.length; idx += 1) {
1242 | const result = expType.validate (values[idx]);
1243 | if (result.isLeft) {
1244 | return Left (() =>
1245 | invalidValue (typeInfo,
1246 | index,
1247 | [...propPath, ...result.value.propPath],
1248 | result.value.value)
1249 | );
1250 | }
1251 | }
1252 |
1253 | switch (expType.type) {
1254 | case VARIABLE: {
1255 | const typeVarName = expType.name;
1256 | const {constraints} = typeInfo;
1257 | if (hasOwnProperty.call (constraints, typeVarName)) {
1258 | const typeClasses = constraints[typeVarName];
1259 | for (let idx = 0; idx < values.length; idx += 1) {
1260 | for (let idx2 = 0; idx2 < typeClasses.length; idx2 += 1) {
1261 | if (!(typeClasses[idx2].test (values[idx]))) {
1262 | return Left (() =>
1263 | typeClassConstraintViolation (
1264 | typeInfo,
1265 | typeClasses[idx2],
1266 | index,
1267 | propPath,
1268 | values[idx]
1269 | )
1270 | );
1271 | }
1272 | }
1273 | }
1274 | }
1275 |
1276 | const typeVarMap$ = updateTypeVarMap (typeVarMap,
1277 | expType,
1278 | index,
1279 | propPath,
1280 | values);
1281 |
1282 | const okTypes = typeVarMap$[typeVarName].types;
1283 | return (
1284 | okTypes.length === 0
1285 | ? Left (() =>
1286 | typeVarConstraintViolation (
1287 | typeInfo,
1288 | index,
1289 | propPath,
1290 | typeVarMap$[typeVarName].valuesByPath
1291 | )
1292 | )
1293 | : Z.reduce ((e, t) => (
1294 | Z.chain (r => {
1295 | // The `a` in `Functor f => f a` corresponds to the `a`
1296 | // in `Maybe a` but to the `b` in `Either a b`. A type
1297 | // variable's $1 will correspond to either $1 or $2 of
1298 | // the actual type depending on the actual type's arity.
1299 | const offset = t.arity - expType.arity;
1300 | return expType.keys.reduce ((e, k, idx) => {
1301 | const extractor = t.extractors[t.keys[offset + idx]];
1302 | return Z.reduce ((e, x) => (
1303 | Z.chain (r => satisfactoryTypes (
1304 | typeInfo,
1305 | r.typeVarMap,
1306 | expType.types[k],
1307 | index,
1308 | [...propPath, k],
1309 | [x]
1310 | ), e)
1311 | ), e, Z.chain (extractor, values));
1312 | }, Right (r));
1313 | }, e)
1314 | ), Right ({typeVarMap: typeVarMap$, types: okTypes}), okTypes)
1315 | );
1316 | }
1317 | case UNARY: {
1318 | return Z.map (
1319 | result => ({
1320 | typeVarMap: result.typeVarMap,
1321 | types: Z.map (
1322 | _UnaryType (expType.name)
1323 | (expType.url)
1324 | (expType.supertypes)
1325 | (expType.test)
1326 | (expType._extractors.$1),
1327 | result.types.length > 0
1328 | ? result.types
1329 | /* c8 ignore next */
1330 | : [expType.types.$1]
1331 | ),
1332 | }),
1333 | satisfactoryTypes (
1334 | typeInfo,
1335 | typeVarMap,
1336 | expType.types.$1,
1337 | index,
1338 | [...propPath, '$1'],
1339 | Z.chain (expType.extractors.$1, values)
1340 | )
1341 | );
1342 | }
1343 | case BINARY: {
1344 | return Z.chain (
1345 | result => {
1346 | const $1s = result.types;
1347 | return Z.map (
1348 | result => {
1349 | const $2s = result.types;
1350 | return {
1351 | typeVarMap: result.typeVarMap,
1352 | types: Z.lift2 (_BinaryType (expType.name)
1353 | (expType.url)
1354 | (expType.supertypes)
1355 | (expType.test)
1356 | (expType._extractors.$1)
1357 | (expType._extractors.$2),
1358 | /* c8 ignore next */
1359 | $1s.length > 0 ? $1s : [expType.types.$1],
1360 | /* c8 ignore next */
1361 | $2s.length > 0 ? $2s : [expType.types.$2]),
1362 | };
1363 | },
1364 | satisfactoryTypes (
1365 | typeInfo,
1366 | result.typeVarMap,
1367 | expType.types.$2,
1368 | index,
1369 | [...propPath, '$2'],
1370 | Z.chain (expType.extractors.$2, values)
1371 | )
1372 | );
1373 | },
1374 | satisfactoryTypes (
1375 | typeInfo,
1376 | typeVarMap,
1377 | expType.types.$1,
1378 | index,
1379 | [...propPath, '$1'],
1380 | Z.chain (expType.extractors.$1, values)
1381 | )
1382 | );
1383 | }
1384 | case RECORD: {
1385 | return Z.reduce ((e, k) => (
1386 | Z.chain (r => satisfactoryTypes (
1387 | typeInfo,
1388 | r.typeVarMap,
1389 | expType.types[k],
1390 | index,
1391 | [...propPath, k],
1392 | Z.chain (expType.extractors[k], values)
1393 | ), e)
1394 | ), Right ({typeVarMap, types: [expType]}), expType.keys);
1395 | }
1396 | default: {
1397 | return Right ({typeVarMap, types: [expType]});
1398 | }
1399 | }
1400 | }
1401 |
1402 | //# test :: Type -> a -> Boolean
1403 | //.
1404 | //. Takes a type and any value. Returns `true` if the value is a member
1405 | //. of the type; `false` otherwise.
1406 | export const test = _def
1407 | ('test')
1408 | ({})
1409 | ([Type, Any, Boolean])
1410 | (t => x => {
1411 | const typeInfo = {name: 'name', constraints: {}, types: [t]};
1412 | return (satisfactoryTypes (typeInfo, {}, t, 0, [], [x])).isRight;
1413 | });
1414 |
1415 | //. ### Type constructors
1416 | //.
1417 | //. sanctuary-def provides several functions for defining types.
1418 |
1419 | //# NullaryType :: String -> String -> Array Type -> (Any -> Boolean) -> Type
1420 | //.
1421 | //. Type constructor for types with no type variables (such as [`Number`][]).
1422 | //.
1423 | //. To define a nullary type `t` one must provide:
1424 | //.
1425 | //. - the name of `t` (exposed as `t.name`);
1426 | //.
1427 | //. - the documentation URL of `t` (exposed as `t.url`);
1428 | //.
1429 | //. - an array of supertypes (exposed as `t.supertypes`); and
1430 | //.
1431 | //. - a predicate that accepts any value that is a member of every one of
1432 | //. the given supertypes, and returns `true` if (and only if) the value
1433 | //. is a member of `t`.
1434 | //.
1435 | //. For example:
1436 | //.
1437 | //. ```javascript
1438 | //. // Integer :: Type
1439 | //. const Integer = $.NullaryType
1440 | //. ('Integer')
1441 | //. ('http://example.com/my-package#Integer')
1442 | //. ([])
1443 | //. (x => typeof x === 'number' &&
1444 | //. Math.floor (x) === x &&
1445 | //. x >= Number.MIN_SAFE_INTEGER &&
1446 | //. x <= Number.MAX_SAFE_INTEGER);
1447 | //.
1448 | //. // NonZeroInteger :: Type
1449 | //. const NonZeroInteger = $.NullaryType
1450 | //. ('NonZeroInteger')
1451 | //. ('http://example.com/my-package#NonZeroInteger')
1452 | //. ([Integer])
1453 | //. (x => x !== 0);
1454 | //.
1455 | //. // rem :: Integer -> NonZeroInteger -> Integer
1456 | //. const rem =
1457 | //. $.def ('rem')
1458 | //. ({})
1459 | //. ([Integer, NonZeroInteger, Integer])
1460 | //. (x => y => x % y);
1461 | //.
1462 | //. rem (42) (5);
1463 | //. // => 2
1464 | //.
1465 | //. rem (0.5);
1466 | //. // ! TypeError: Invalid value
1467 | //. //
1468 | //. // rem :: Integer -> NonZeroInteger -> Integer
1469 | //. // ^^^^^^^
1470 | //. // 1
1471 | //. //
1472 | //. // 1) 0.5 :: Number
1473 | //. //
1474 | //. // The value at position 1 is not a member of ‘Integer’.
1475 | //. //
1476 | //. // See http://example.com/my-package#Integer for information about the Integer type.
1477 | //.
1478 | //. rem (42) (0);
1479 | //. // ! TypeError: Invalid value
1480 | //. //
1481 | //. // rem :: Integer -> NonZeroInteger -> Integer
1482 | //. // ^^^^^^^^^^^^^^
1483 | //. // 1
1484 | //. //
1485 | //. // 1) 0 :: Number
1486 | //. //
1487 | //. // The value at position 1 is not a member of ‘NonZeroInteger’.
1488 | //. //
1489 | //. // See http://example.com/my-package#NonZeroInteger for information about the NonZeroInteger type.
1490 | //. ```
1491 | function _NullaryType(name) {
1492 | return url => supertypes => test => (
1493 | _Type (NULLARY, name, url, 0, null, supertypes, test, [])
1494 | );
1495 | }
1496 | export const NullaryType = _def
1497 | ('NullaryType')
1498 | ({})
1499 | ([String,
1500 | String,
1501 | Array (Type),
1502 | Unchecked ('(Any -> Boolean)'),
1503 | Type])
1504 | (_NullaryType);
1505 |
1506 | //# UnaryType :: Foldable f => String -> String -> Array Type -> (Any -> Boolean) -> (t a -> f a) -> Type -> Type
1507 | //.
1508 | //. Type constructor for types with one type variable (such as [`Array`][]).
1509 | //.
1510 | //. To define a unary type `t a` one must provide:
1511 | //.
1512 | //. - the name of `t` (exposed as `t.name`);
1513 | //.
1514 | //. - the documentation URL of `t` (exposed as `t.url`);
1515 | //.
1516 | //. - an array of supertypes (exposed as `t.supertypes`);
1517 | //.
1518 | //. - a predicate that accepts any value that is a member of every one of
1519 | //. the given supertypes, and returns `true` if (and only if) the value
1520 | //. is a member of `t x` for some type `x`;
1521 | //.
1522 | //. - a function that takes any value of type `t a` and returns the values
1523 | //. of type `a` contained in the `t`; and
1524 | //.
1525 | //. - the type of `a`.
1526 | //.
1527 | //. For example:
1528 | //.
1529 | //. ```javascript
1530 | //. const show = require ('sanctuary-show');
1531 | //. const type = require ('sanctuary-type-identifiers');
1532 | //.
1533 | //. // maybeTypeIdent :: String
1534 | //. const maybeTypeIdent = 'my-package/Maybe';
1535 | //.
1536 | //. // Maybe :: Type -> Type
1537 | //. const Maybe = $.UnaryType
1538 | //. ('Maybe')
1539 | //. ('http://example.com/my-package#Maybe')
1540 | //. ([])
1541 | //. (x => type (x) === maybeTypeIdent)
1542 | //. (maybe => maybe.isJust ? [maybe.value] : []);
1543 | //.
1544 | //. // Nothing :: Maybe a
1545 | //. const Nothing = {
1546 | //. 'isJust': false,
1547 | //. 'isNothing': true,
1548 | //. '@@type': maybeTypeIdent,
1549 | //. '@@show': () => 'Nothing',
1550 | //. };
1551 | //.
1552 | //. // Just :: a -> Maybe a
1553 | //. const Just = x => ({
1554 | //. 'isJust': true,
1555 | //. 'isNothing': false,
1556 | //. '@@type': maybeTypeIdent,
1557 | //. '@@show': () => `Just (${show (x)})`,
1558 | //. 'value': x,
1559 | //. });
1560 | //.
1561 | //. // fromMaybe :: a -> Maybe a -> a
1562 | //. const fromMaybe =
1563 | //. $.def ('fromMaybe')
1564 | //. ({})
1565 | //. ([a, Maybe (a), a])
1566 | //. (x => m => m.isJust ? m.value : x);
1567 | //.
1568 | //. fromMaybe (0) (Just (42));
1569 | //. // => 42
1570 | //.
1571 | //. fromMaybe (0) (Nothing);
1572 | //. // => 0
1573 | //.
1574 | //. fromMaybe (0) (Just ('XXX'));
1575 | //. // ! TypeError: Type-variable constraint violation
1576 | //. //
1577 | //. // fromMaybe :: a -> Maybe a -> a
1578 | //. // ^ ^
1579 | //. // 1 2
1580 | //. //
1581 | //. // 1) 0 :: Number
1582 | //. //
1583 | //. // 2) "XXX" :: String
1584 | //. //
1585 | //. // Since there is no type of which all the above values are members, the type-variable constraint has been violated.
1586 | //. ```
1587 | function _UnaryType(name) {
1588 | return url => supertypes => test => _1 => $1 => (
1589 | _Type (UNARY,
1590 | name,
1591 | url,
1592 | 1,
1593 | null,
1594 | supertypes,
1595 | test,
1596 | [['$1', _1, $1]])
1597 | );
1598 | }
1599 | export const UnaryType = _def
1600 | ('UnaryType')
1601 | ({f: [Z.Foldable]})
1602 | ([String,
1603 | String,
1604 | Array (Type),
1605 | Unchecked ('(Any -> Boolean)'),
1606 | Unchecked ('(t a -> f a)'),
1607 | Unchecked ('Type -> Type')])
1608 | (name => url => supertypes => test => _1 =>
1609 | _def (name)
1610 | ({})
1611 | ([Type, Type])
1612 | (_UnaryType (name) (url) (supertypes) (test) (_1)));
1613 |
1614 | //# BinaryType :: Foldable f => String -> String -> Array Type -> (Any -> Boolean) -> (t a b -> f a) -> (t a b -> f b) -> Type -> Type -> Type
1615 | //.
1616 | //. Type constructor for types with two type variables (such as
1617 | //. [`Array2`][]).
1618 | //.
1619 | //. To define a binary type `t a b` one must provide:
1620 | //.
1621 | //. - the name of `t` (exposed as `t.name`);
1622 | //.
1623 | //. - the documentation URL of `t` (exposed as `t.url`);
1624 | //.
1625 | //. - an array of supertypes (exposed as `t.supertypes`);
1626 | //.
1627 | //. - a predicate that accepts any value that is a member of every one of
1628 | //. the given supertypes, and returns `true` if (and only if) the value
1629 | //. is a member of `t x y` for some types `x` and `y`;
1630 | //.
1631 | //. - a function that takes any value of type `t a b` and returns the
1632 | //. values of type `a` contained in the `t`;
1633 | //.
1634 | //. - a function that takes any value of type `t a b` and returns the
1635 | //. values of type `b` contained in the `t`;
1636 | //.
1637 | //. - the type of `a`; and
1638 | //.
1639 | //. - the type of `b`.
1640 | //.
1641 | //. For example:
1642 | //.
1643 | //. ```javascript
1644 | //. const type = require ('sanctuary-type-identifiers');
1645 | //.
1646 | //. // pairTypeIdent :: String
1647 | //. const pairTypeIdent = 'my-package/Pair';
1648 | //.
1649 | //. // $Pair :: Type -> Type -> Type
1650 | //. const $Pair = $.BinaryType
1651 | //. ('Pair')
1652 | //. ('http://example.com/my-package#Pair')
1653 | //. ([])
1654 | //. (x => type (x) === pairTypeIdent)
1655 | //. (({fst}) => [fst])
1656 | //. (({snd}) => [snd]);
1657 | //.
1658 | //. // Pair :: a -> b -> Pair a b
1659 | //. const Pair =
1660 | //. $.def ('Pair')
1661 | //. ({})
1662 | //. ([a, b, $Pair (a) (b)])
1663 | //. (fst => snd => ({
1664 | //. 'fst': fst,
1665 | //. 'snd': snd,
1666 | //. '@@type': pairTypeIdent,
1667 | //. '@@show': () => `Pair (${show (fst)}) (${show (snd)})`,
1668 | //. }));
1669 | //.
1670 | //. // Rank :: Type
1671 | //. const Rank = $.NullaryType
1672 | //. ('Rank')
1673 | //. ('http://example.com/my-package#Rank')
1674 | //. ([$.String])
1675 | //. (x => /^(A|2|3|4|5|6|7|8|9|10|J|Q|K)$/.test (x));
1676 | //.
1677 | //. // Suit :: Type
1678 | //. const Suit = $.NullaryType
1679 | //. ('Suit')
1680 | //. ('http://example.com/my-package#Suit')
1681 | //. ([$.String])
1682 | //. (x => /^[\u2660\u2663\u2665\u2666]$/.test (x));
1683 | //.
1684 | //. // Card :: Type
1685 | //. const Card = $Pair (Rank) (Suit);
1686 | //.
1687 | //. // showCard :: Card -> String
1688 | //. const showCard =
1689 | //. $.def ('showCard')
1690 | //. ({})
1691 | //. ([Card, $.String])
1692 | //. (card => card.fst + card.snd);
1693 | //.
1694 | //. showCard (Pair ('A') ('♠'));
1695 | //. // => 'A♠'
1696 | //.
1697 | //. showCard (Pair ('X') ('♠'));
1698 | //. // ! TypeError: Invalid value
1699 | //. //
1700 | //. // showCard :: Pair Rank Suit -> String
1701 | //. // ^^^^
1702 | //. // 1
1703 | //. //
1704 | //. // 1) "X" :: String
1705 | //. //
1706 | //. // The value at position 1 is not a member of ‘Rank’.
1707 | //. //
1708 | //. // See http://example.com/my-package#Rank for information about the Rank type.
1709 | //. ```
1710 | function _BinaryType(name) {
1711 | return url => supertypes => test => _1 => _2 => $1 => $2 => (
1712 | _Type (BINARY,
1713 | name,
1714 | url,
1715 | 2,
1716 | null,
1717 | supertypes,
1718 | test,
1719 | [['$1', _1, $1],
1720 | ['$2', _2, $2]])
1721 | );
1722 | }
1723 | export const BinaryType = _def
1724 | ('BinaryType')
1725 | ({f: [Z.Foldable]})
1726 | ([String,
1727 | String,
1728 | Array (Type),
1729 | Unchecked ('(Any -> Boolean)'),
1730 | Unchecked ('(t a b -> f a)'),
1731 | Unchecked ('(t a b -> f b)'),
1732 | Unchecked ('Type -> Type -> Type')])
1733 | (name => url => supertypes => test => _1 => _2 =>
1734 | _def (name)
1735 | ({})
1736 | ([Type, Type, Type])
1737 | (_BinaryType (name) (url) (supertypes) (test) (_1) (_2)));
1738 |
1739 | //# EnumType :: String -> String -> Array Any -> Type
1740 | //.
1741 | //. Type constructor for [enumerated types][] (such as [`RegexFlags`][]).
1742 | //.
1743 | //. To define an enumerated type `t` one must provide:
1744 | //.
1745 | //. - the name of `t` (exposed as `t.name`);
1746 | //.
1747 | //. - the documentation URL of `t` (exposed as `t.url`); and
1748 | //.
1749 | //. - an array of distinct values.
1750 | //.
1751 | //. For example:
1752 | //.
1753 | //. ```javascript
1754 | //. // Denomination :: Type
1755 | //. const Denomination = $.EnumType
1756 | //. ('Denomination')
1757 | //. ('http://example.com/my-package#Denomination')
1758 | //. ([10, 20, 50, 100, 200]);
1759 | //. ```
1760 | export const EnumType = _def
1761 | ('EnumType')
1762 | ({})
1763 | ([String, String, Array (Any), Type])
1764 | (name => url => members =>
1765 | _NullaryType (name)
1766 | (url)
1767 | ([])
1768 | (x => members.some (m => Z.equals (x, m))));
1769 |
1770 | //# RecordType :: StrMap Type -> Type
1771 | //.
1772 | //. `RecordType` is used to construct anonymous record types. The type
1773 | //. definition specifies the name and type of each required field. A field is
1774 | //. an enumerable property (either an own property or an inherited property).
1775 | //.
1776 | //. To define an anonymous record type one must provide:
1777 | //.
1778 | //. - an object mapping field name to type.
1779 | //.
1780 | //. For example:
1781 | //.
1782 | //. ```javascript
1783 | //. // Point :: Type
1784 | //. const Point = $.RecordType ({x: $.FiniteNumber, y: $.FiniteNumber});
1785 | //.
1786 | //. // dist :: Point -> Point -> FiniteNumber
1787 | //. const dist =
1788 | //. $.def ('dist')
1789 | //. ({})
1790 | //. ([Point, Point, $.FiniteNumber])
1791 | //. (p => q => Math.sqrt (Math.pow (p.x - q.x, 2) +
1792 | //. Math.pow (p.y - q.y, 2)));
1793 | //.
1794 | //. dist ({x: 0, y: 0}) ({x: 3, y: 4});
1795 | //. // => 5
1796 | //.
1797 | //. dist ({x: 0, y: 0}) ({x: 3, y: 4, color: 'red'});
1798 | //. // => 5
1799 | //.
1800 | //. dist ({x: 0, y: 0}) ({x: NaN, y: NaN});
1801 | //. // ! TypeError: Invalid value
1802 | //. //
1803 | //. // dist :: { x :: FiniteNumber, y :: FiniteNumber } -> { x :: FiniteNumber, y :: FiniteNumber } -> FiniteNumber
1804 | //. // ^^^^^^^^^^^^
1805 | //. // 1
1806 | //. //
1807 | //. // 1) NaN :: Number
1808 | //. //
1809 | //. // The value at position 1 is not a member of ‘FiniteNumber’.
1810 | //.
1811 | //. dist (0);
1812 | //. // ! TypeError: Invalid value
1813 | //. //
1814 | //. // dist :: { x :: FiniteNumber, y :: FiniteNumber } -> { x :: FiniteNumber, y :: FiniteNumber } -> FiniteNumber
1815 | //. // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1816 | //. // 1
1817 | //. //
1818 | //. // 1) 0 :: Number
1819 | //. //
1820 | //. // The value at position 1 is not a member of ‘{ x :: FiniteNumber, y :: FiniteNumber }’.
1821 | //. ```
1822 | export const RecordType = _def
1823 | ('RecordType')
1824 | ({})
1825 | ([StrMap (Type), Type])
1826 | (fields => {
1827 | const keys = globalThis.Object.keys (fields);
1828 | return _Type (
1829 | RECORD,
1830 | '',
1831 | '',
1832 | 0,
1833 | (outer, inner) => {
1834 | if (keys.length === 0) return outer ('{}');
1835 | const reprs = Z.map (k => {
1836 | const t = fields[k];
1837 | return outer (' ') +
1838 | outer (/^(?!\d)[$\w]+$/.test (k) ? k : show (k)) +
1839 | outer (' :: ') +
1840 | inner (k) (show (t));
1841 | }, keys);
1842 | return outer ('{') + reprs.join (outer (',')) + outer (' }');
1843 | },
1844 | [],
1845 | x => {
1846 | if (x == null) return false;
1847 | const missing = {};
1848 | keys.forEach (k => { missing[k] = k; });
1849 | for (const k in x) delete missing[k];
1850 | return Z.size (missing) === 0;
1851 | },
1852 | keys.map (k => [k, x => [x[k]], fields[k]])
1853 | );
1854 | });
1855 |
1856 | //# NamedRecordType :: NonEmpty String -> String -> Array Type -> StrMap Type -> Type
1857 | //.
1858 | //. `NamedRecordType` is used to construct named record types. The type
1859 | //. definition specifies the name and type of each required field. A field is
1860 | //. an enumerable property (either an own property or an inherited property).
1861 | //.
1862 | //. To define a named record type `t` one must provide:
1863 | //.
1864 | //. - the name of `t` (exposed as `t.name`);
1865 | //.
1866 | //. - the documentation URL of `t` (exposed as `t.url`);
1867 | //.
1868 | //. - an array of supertypes (exposed as `t.supertypes`); and
1869 | //.
1870 | //. - an object mapping field name to type.
1871 | //.
1872 | //. For example:
1873 | //.
1874 | //. ```javascript
1875 | //. // Circle :: Type
1876 | //. const Circle = $.NamedRecordType
1877 | //. ('my-package/Circle')
1878 | //. ('http://example.com/my-package#Circle')
1879 | //. ([])
1880 | //. ({radius: $.PositiveFiniteNumber});
1881 | //.
1882 | //. // Cylinder :: Type
1883 | //. const Cylinder = $.NamedRecordType
1884 | //. ('Cylinder')
1885 | //. ('http://example.com/my-package#Cylinder')
1886 | //. ([Circle])
1887 | //. ({height: $.PositiveFiniteNumber});
1888 | //.
1889 | //. // volume :: Cylinder -> PositiveFiniteNumber
1890 | //. const volume =
1891 | //. $.def ('volume')
1892 | //. ({})
1893 | //. ([Cylinder, $.FiniteNumber])
1894 | //. (cyl => Math.PI * cyl.radius * cyl.radius * cyl.height);
1895 | //.
1896 | //. volume ({radius: 2, height: 10});
1897 | //. // => 125.66370614359172
1898 | //.
1899 | //. volume ({radius: 2});
1900 | //. // ! TypeError: Invalid value
1901 | //. //
1902 | //. // volume :: Cylinder -> FiniteNumber
1903 | //. // ^^^^^^^^
1904 | //. // 1
1905 | //. //
1906 | //. // 1) {"radius": 2} :: Object, StrMap Number
1907 | //. //
1908 | //. // The value at position 1 is not a member of ‘Cylinder’.
1909 | //. //
1910 | //. // See http://example.com/my-package#Cylinder for information about the Cylinder type.
1911 | //. ```
1912 | export const NamedRecordType = _def
1913 | ('NamedRecordType')
1914 | ({})
1915 | ([NonEmpty (String), String, Array (Type), StrMap (Type), Type])
1916 | (name => url => supertypes => fields => {
1917 | const keys = Z.sort (globalThis.Object.keys (fields));
1918 | return _Type (
1919 | RECORD,
1920 | name,
1921 | url,
1922 | 0,
1923 | (outer, inner) => outer (name),
1924 | supertypes,
1925 | x => {
1926 | if (x == null) return false;
1927 | const missing = {};
1928 | keys.forEach (k => { missing[k] = k; });
1929 | for (const k in x) delete missing[k];
1930 | return Z.size (missing) === 0 &&
1931 | keys.every (k => _test (x[k]) (fields[k]));
1932 | },
1933 | keys.map (k => [k, x => [x[k]], fields[k]])
1934 | );
1935 | });
1936 |
1937 | //# TypeVariable :: String -> Type
1938 | //.
1939 | //. Polymorphism is powerful. Not being able to define a function for
1940 | //. all types would be very limiting indeed: one couldn't even define the
1941 | //. identity function!
1942 | //.
1943 | //. Before defining a polymorphic function one must define one or more type
1944 | //. variables:
1945 | //.
1946 | //. ```javascript
1947 | //. const a = $.TypeVariable ('a');
1948 | //. const b = $.TypeVariable ('b');
1949 | //.
1950 | //. // id :: a -> a
1951 | //. const id = $.def ('id') ({}) ([a, a]) (x => x);
1952 | //.
1953 | //. id (42);
1954 | //. // => 42
1955 | //.
1956 | //. id (null);
1957 | //. // => null
1958 | //. ```
1959 | //.
1960 | //. The same type variable may be used in multiple positions, creating a
1961 | //. constraint:
1962 | //.
1963 | //. ```javascript
1964 | //. // cmp :: a -> a -> Number
1965 | //. const cmp =
1966 | //. $.def ('cmp')
1967 | //. ({})
1968 | //. ([a, a, $.Number])
1969 | //. (x => y => x < y ? -1 : x > y ? 1 : 0);
1970 | //.
1971 | //. cmp (42) (42);
1972 | //. // => 0
1973 | //.
1974 | //. cmp ('a') ('z');
1975 | //. // => -1
1976 | //.
1977 | //. cmp ('z') ('a');
1978 | //. // => 1
1979 | //.
1980 | //. cmp (0) ('1');
1981 | //. // ! TypeError: Type-variable constraint violation
1982 | //. //
1983 | //. // cmp :: a -> a -> Number
1984 | //. // ^ ^
1985 | //. // 1 2
1986 | //. //
1987 | //. // 1) 0 :: Number
1988 | //. //
1989 | //. // 2) "1" :: String
1990 | //. //
1991 | //. // Since there is no type of which all the above values are members, the type-variable constraint has been violated.
1992 | //. ```
1993 | export const TypeVariable = _def
1994 | ('TypeVariable')
1995 | ({})
1996 | ([String, Type])
1997 | (name =>
1998 | _Type (VARIABLE,
1999 | name,
2000 | '',
2001 | 0,
2002 | (outer, inner) => name,
2003 | [],
2004 | x => config.env.some (t => t.arity >= 0 && _test (x) (t)),
2005 | []));
2006 |
2007 | //# UnaryTypeVariable :: String -> Type -> Type
2008 | //.
2009 | //. Combines [`UnaryType`][] and [`TypeVariable`][].
2010 | //.
2011 | //. To define a unary type variable `t a` one must provide:
2012 | //.
2013 | //. - a name (conventionally matching `^[a-z]$`); and
2014 | //.
2015 | //. - the type of `a`.
2016 | //.
2017 | //. Consider the type of a generalized `map`:
2018 | //.
2019 | //. ```haskell
2020 | //. map :: Functor f => (a -> b) -> f a -> f b
2021 | //. ```
2022 | //.
2023 | //. `f` is a unary type variable. With two (nullary) type variables, one
2024 | //. unary type variable, and one [type class][] it's possible to define a
2025 | //. fully polymorphic `map` function:
2026 | //.
2027 | //. ```javascript
2028 | //. const $ = require ('sanctuary-def');
2029 | //. const Z = require ('sanctuary-type-classes');
2030 | //.
2031 | //. const a = $.TypeVariable ('a');
2032 | //. const b = $.TypeVariable ('b');
2033 | //. const f = $.UnaryTypeVariable ('f');
2034 | //.
2035 | //. // map :: Functor f => (a -> b) -> f a -> f b
2036 | //. const map =
2037 | //. $.def ('map')
2038 | //. ({f: [Z.Functor]})
2039 | //. ([$.Function ([a, b]), f (a), f (b)])
2040 | //. (f => functor => Z.map (f, functor));
2041 | //. ```
2042 | //.
2043 | //. Whereas a regular type variable is fully resolved (`a` might become
2044 | //. `Array (Array String)`, for example), a unary type variable defers to
2045 | //. its type argument, which may itself be a type variable. The type argument
2046 | //. corresponds to the type argument of a unary type or the *second* type
2047 | //. argument of a binary type. The second type argument of `Map k v`, for
2048 | //. example, is `v`. One could replace `Functor => f` with `Map k` or with
2049 | //. `Map Integer`, but not with `Map`.
2050 | //.
2051 | //. This shallow inspection makes it possible to constrain a value's "outer"
2052 | //. and "inner" types independently.
2053 | export const UnaryTypeVariable = _def
2054 | ('UnaryTypeVariable')
2055 | ({})
2056 | ([String, Unchecked ('Type -> Type')])
2057 | (name =>
2058 | _def (name)
2059 | ({})
2060 | ([Type, Type])
2061 | ($1 =>
2062 | _Type (VARIABLE,
2063 | name,
2064 | '',
2065 | 1,
2066 | null,
2067 | [],
2068 | x => config.env.some (t => t.arity >= 1 && _test (x) (t)),
2069 | [['$1', x => [], $1]])));
2070 |
2071 | //# BinaryTypeVariable :: String -> Type -> Type -> Type
2072 | //.
2073 | //. Combines [`BinaryType`][] and [`TypeVariable`][].
2074 | //.
2075 | //. To define a binary type variable `t a b` one must provide:
2076 | //.
2077 | //. - a name (conventionally matching `^[a-z]$`);
2078 | //.
2079 | //. - the type of `a`; and
2080 | //.
2081 | //. - the type of `b`.
2082 | //.
2083 | //. The more detailed explanation of [`UnaryTypeVariable`][] also applies to
2084 | //. `BinaryTypeVariable`.
2085 | export const BinaryTypeVariable = _def
2086 | ('BinaryTypeVariable')
2087 | ({})
2088 | ([String, Unchecked ('Type -> Type -> Type')])
2089 | (name =>
2090 | _def (name)
2091 | ({})
2092 | ([Type, Type, Type])
2093 | ($1 => $2 =>
2094 | _Type (VARIABLE,
2095 | name,
2096 | '',
2097 | 2,
2098 | null,
2099 | [],
2100 | x => config.env.some (t => t.arity >= 2 && _test (x) (t)),
2101 | [['$1', x => [], $1],
2102 | ['$2', x => [], $2]])));
2103 |
2104 | //# Thunk :: Type -> Type
2105 | //.
2106 | //. `$.Thunk (T)` is shorthand for `$.Function ([T])`, the type comprising
2107 | //. every nullary function (thunk) that returns a value of type `T`.
2108 | export const Thunk = _def
2109 | ('Thunk')
2110 | ({})
2111 | ([Type, Type])
2112 | (t => Function ([t]));
2113 |
2114 | //# Predicate :: Type -> Type
2115 | //.
2116 | //. `$.Predicate (T)` is shorthand for `$.Fn (T) ($.Boolean)`, the type
2117 | //. comprising every predicate function that takes a value of type `T`.
2118 | export const Predicate = _def
2119 | ('Predicate')
2120 | ({})
2121 | ([Type, Type])
2122 | (t => Fn (t) (Boolean));
2123 |
2124 | //. ### Type classes
2125 | //.
2126 | //. One can trivially define a function of type `String -> String -> String`
2127 | //. that concatenates two strings. This is overly restrictive, though, since
2128 | //. other types support concatenation (`Array a`, for example).
2129 | //.
2130 | //. One could use a type variable to define a polymorphic "concat" function:
2131 | //.
2132 | //. ```javascript
2133 | //. // _concat :: a -> a -> a
2134 | //. const _concat =
2135 | //. $.def ('_concat')
2136 | //. ({})
2137 | //. ([a, a, a])
2138 | //. (x => y => x.concat (y));
2139 | //.
2140 | //. _concat ('fizz') ('buzz');
2141 | //. // => 'fizzbuzz'
2142 | //.
2143 | //. _concat ([1, 2]) ([3, 4]);
2144 | //. // => [1, 2, 3, 4]
2145 | //.
2146 | //. _concat ([1, 2]) ('buzz');
2147 | //. // ! TypeError: Type-variable constraint violation
2148 | //. //
2149 | //. // _concat :: a -> a -> a
2150 | //. // ^ ^
2151 | //. // 1 2
2152 | //. //
2153 | //. // 1) [1, 2] :: Array Number
2154 | //. //
2155 | //. // 2) "buzz" :: String
2156 | //. //
2157 | //. // Since there is no type of which all the above values are members, the type-variable constraint has been violated.
2158 | //. ```
2159 | //.
2160 | //. The type of `_concat` is misleading: it suggests that it can operate on
2161 | //. any two values of *any* one type. In fact there's an implicit constraint,
2162 | //. since the type must support concatenation (in [mathematical][semigroup]
2163 | //. terms, the type must have a [semigroup][FL:Semigroup]). Violating this
2164 | //. implicit constraint results in a run-time error in the implementation:
2165 | //.
2166 | //. ```javascript
2167 | //. _concat (null) (null);
2168 | //. // ! TypeError: Cannot read property 'concat' of null
2169 | //. ```
2170 | //.
2171 | //. The solution is to constrain `a` by first defining a [`TypeClass`][]
2172 | //. value, then specifying the constraint in the definition of the "concat"
2173 | //. function:
2174 | //.
2175 | //. ```javascript
2176 | //. const Z = require ('sanctuary-type-classes');
2177 | //.
2178 | //. // Semigroup :: TypeClass
2179 | //. const Semigroup = Z.TypeClass (
2180 | //. 'my-package/Semigroup',
2181 | //. 'http://example.com/my-package#Semigroup',
2182 | //. [],
2183 | //. x => x != null && typeof x.concat === 'function'
2184 | //. );
2185 | //.
2186 | //. // concat :: Semigroup a => a -> a -> a
2187 | //. const concat =
2188 | //. $.def ('concat')
2189 | //. ({a: [Semigroup]})
2190 | //. ([a, a, a])
2191 | //. (x => y => x.concat (y));
2192 | //.
2193 | //. concat ([1, 2]) ([3, 4]);
2194 | //. // => [1, 2, 3, 4]
2195 | //.
2196 | //. concat (null) (null);
2197 | //. // ! TypeError: Type-class constraint violation
2198 | //. //
2199 | //. // concat :: Semigroup a => a -> a -> a
2200 | //. // ^^^^^^^^^^^ ^
2201 | //. // 1
2202 | //. //
2203 | //. // 1) null :: Null
2204 | //. //
2205 | //. // ‘concat’ requires ‘a’ to satisfy the Semigroup type-class constraint; the value at position 1 does not.
2206 | //. //
2207 | //. // See http://example.com/my-package#Semigroup for information about the my-package/Semigroup type class.
2208 | //. ```
2209 | //.
2210 | //. Multiple constraints may be placed on a type variable by including
2211 | //. multiple `TypeClass` values in the array (e.g. `{a: [Foo, Bar, Baz]}`).
2212 |
2213 | // invalidArgumentsCount :: (TypeInfo, Integer, Integer, Array Any) -> Error
2214 | //
2215 | // This function is used in `curry` when a function defined via `def`
2216 | // is applied to too many arguments.
2217 | const invalidArgumentsCount = (typeInfo, index, numArgsExpected, args) => (
2218 | new TypeError (
2219 | `‘${
2220 | typeInfo.name
2221 | }’ applied to the wrong number of arguments\n\n${
2222 | underline_ (typeInfo)
2223 | (index_ => f => t => propPath => s =>
2224 | index_ === index ? f (s) : ' '.repeat (s.length))
2225 | }\nExpected ${
2226 | numArgs (numArgsExpected)
2227 | } but received ${
2228 | numArgs (args.length)
2229 | }${
2230 | args.length === 0
2231 | /* c8 ignore next */
2232 | ? '.\n'
2233 | : ':\n\n' + Z.foldMap (globalThis.String, x => ` - ${show (x)}\n`, args)
2234 | }`
2235 | )
2236 | );
2237 |
2238 | // constraintsRepr :: ... -> String
2239 | const constraintsRepr = (
2240 | constraints, // :: StrMap (Array TypeClass)
2241 | outer, // :: String -> String
2242 | inner // :: String -> TypeClass -> String -> String
2243 | ) => {
2244 | const reprs = Z.chain (
2245 | k => (
2246 | constraints[k].map (typeClass =>
2247 | inner (k) (typeClass) (`${stripNamespace (typeClass)} ${k}`)
2248 | )
2249 | ),
2250 | globalThis.Object.keys (constraints)
2251 | );
2252 | switch (reprs.length) {
2253 | case 0:
2254 | return '';
2255 | case 1:
2256 | return reprs.join (outer (', ')) + outer (' => ');
2257 | default:
2258 | return outer ('(') + reprs.join (outer (', ')) + outer (') => ');
2259 | }
2260 | };
2261 |
2262 | // label :: String -> String -> String
2263 | const label = label => s => {
2264 | const delta = s.length - label.length;
2265 | return ' '.repeat (Math.floor (delta / 2)) + label +
2266 | ' '.repeat (Math.ceil (delta / 2));
2267 | };
2268 |
2269 | // typeVarNames :: Type -> Array String
2270 | const typeVarNames = t => [
2271 | ...(t.type === VARIABLE ? [t.name] : []),
2272 | ...(Z.chain (k => typeVarNames (t.types[k]), t.keys)),
2273 | ];
2274 |
2275 | // showTypeWith :: Array Type -> Type -> String
2276 | const showTypeWith = types => {
2277 | const names = Z.chain (typeVarNames, types);
2278 | return t => {
2279 | let code = 'a'.charCodeAt (0);
2280 | const repr = (
2281 | show (t)
2282 | .replace (/\bUnknown\b/g, () => {
2283 | let name;
2284 | // eslint-disable-next-line no-plusplus
2285 | do name = globalThis.String.fromCharCode (code++);
2286 | while (names.indexOf (name) >= 0);
2287 | return name;
2288 | })
2289 | );
2290 | return t.type === FUNCTION ? '(' + repr + ')' : repr;
2291 | };
2292 | };
2293 |
2294 | // showValuesAndTypes :: (TypeInfo, Array Any, Integer) -> String
2295 | const showValuesAndTypes = (typeInfo, values, pos) => {
2296 | const showType = showTypeWith (typeInfo.types);
2297 | return `${
2298 | show (pos)
2299 | }) ${
2300 | values
2301 | .map (x => {
2302 | const types = determineActualTypesLoose ([x]);
2303 | return `${
2304 | show (x)
2305 | } :: ${
2306 | types.length > 0 ? (types.map (showType)).join (', ') : '(no types)'
2307 | }`;
2308 | })
2309 | .join ('\n ')
2310 | }`;
2311 | };
2312 |
2313 | // typeSignature :: TypeInfo -> String
2314 | const typeSignature = typeInfo => `${
2315 | typeInfo.name
2316 | } :: ${
2317 | constraintsRepr (typeInfo.constraints, s => s, tvn => tc => s => s)
2318 | }${
2319 | typeInfo.types
2320 | .map (showTypeWith (typeInfo.types))
2321 | .join (' -> ')
2322 | }`;
2323 |
2324 | // _underline :: ... -> String
2325 | const _underline = (
2326 | t, // :: Type
2327 | propPath, // :: PropPath
2328 | formatType3 // :: Type -> Array String -> String -> String
2329 | ) => (
2330 | formatType3 (t)
2331 | (propPath)
2332 | (t.format (s => ' '.repeat (s.length),
2333 | k => s => _underline (t.types[k],
2334 | [...propPath, k],
2335 | formatType3)))
2336 | );
2337 |
2338 | // underline :: ... -> String
2339 | const underline = underlineConstraint => typeInfo => formatType5 => {
2340 | const st = typeInfo.types.reduce ((st, t, index) => {
2341 | const f = f => (
2342 | t.type === FUNCTION
2343 | ? ' ' + _underline (t, [], formatType5 (index) (f)) + ' '
2344 | : _underline (t, [], formatType5 (index) (f))
2345 | );
2346 | st.carets.push (f (s => '^'.repeat (s.length)));
2347 | st.numbers.push (f (s => label (show (st.counter += 1)) (s)));
2348 | return st;
2349 | }, {carets: [], numbers: [], counter: 0});
2350 |
2351 | return (
2352 | `${
2353 | typeSignature (typeInfo)
2354 | }\n${
2355 | ' '.repeat (`${typeInfo.name} :: `.length)
2356 | }${
2357 | constraintsRepr (
2358 | typeInfo.constraints,
2359 | s => ' '.repeat (s.length),
2360 | underlineConstraint
2361 | )
2362 | }${
2363 | st.carets.join (' '.repeat (' -> '.length))
2364 | }\n${
2365 | ' '.repeat (`${typeInfo.name} :: `.length)
2366 | }${
2367 | constraintsRepr (
2368 | typeInfo.constraints,
2369 | s => ' '.repeat (s.length),
2370 | tvn => tc => s => ' '.repeat (s.length)
2371 | )
2372 | }${
2373 | st.numbers.join (' '.repeat (' -> '.length))
2374 | }\n`
2375 | ).replace (/[ ]+$/gm, '');
2376 | };
2377 |
2378 | // underline_ :: ... -> String
2379 | const underline_ = underline (tvn => tc => s => ' '.repeat (s.length));
2380 |
2381 | // formatType6 ::
2382 | // PropPath -> Integer -> (String -> String) ->
2383 | // Type -> PropPath -> String -> String
2384 | const formatType6 = indexedPropPath => index_ => f => t => propPath_ => {
2385 | const indexedPropPath_ = [index_, ...propPath_];
2386 | const p = isPrefix (indexedPropPath_) (indexedPropPath);
2387 | const q = isPrefix (indexedPropPath) (indexedPropPath_);
2388 | return s => p && q ? f (s) : p ? s : ' '.repeat (s.length);
2389 | };
2390 |
2391 | // typeClassConstraintViolation :: ... -> Error
2392 | const typeClassConstraintViolation = (
2393 | typeInfo, // :: TypeInfo
2394 | typeClass, // :: TypeClass
2395 | index, // :: Integer
2396 | propPath, // :: PropPath
2397 | value // :: Any
2398 | ) => {
2399 | const expType = propPath.reduce (
2400 | (t, prop) => t.types[prop],
2401 | typeInfo.types[index]
2402 | );
2403 | return new TypeError (
2404 | `Type-class constraint violation\n\n${
2405 | underline (tvn => tc => s =>
2406 | tvn === expType.name && tc.name === typeClass.name
2407 | ? '^'.repeat (s.length)
2408 | : ' '.repeat (s.length))
2409 | (typeInfo)
2410 | (formatType6 ([index, ...propPath]))
2411 | }\n${
2412 | showValuesAndTypes (typeInfo, [value], 1)
2413 | }\n\n‘${
2414 | typeInfo.name
2415 | }’ requires ‘${
2416 | expType.name
2417 | }’ to satisfy the ${
2418 | stripNamespace (typeClass)
2419 | } type-class constraint; the value at position 1 does not.\n${
2420 | typeClass.url == null ||
2421 | typeClass.url === ''
2422 | /* c8 ignore next */
2423 | ? ''
2424 | : `\nSee ${
2425 | typeClass.url
2426 | } for information about the ${
2427 | typeClass.name
2428 | } type class.\n`
2429 | }`
2430 | );
2431 | };
2432 |
2433 | // typeVarConstraintViolation :: ... -> Error
2434 | const typeVarConstraintViolation = (
2435 | typeInfo, // :: TypeInfo
2436 | index, // :: Integer
2437 | propPath, // :: PropPath
2438 | valuesByPath // :: StrMap (Array Any)
2439 | ) => {
2440 | // If we apply an ‘a -> a -> a -> a’ function to Left ('x'), Right (1),
2441 | // and Right (null) we'd like to avoid underlining the first argument
2442 | // position, since Left ('x') is compatible with the other ‘a’ values.
2443 | const key = JSON.stringify ([index, ...propPath]);
2444 | const values = valuesByPath[key];
2445 |
2446 | // Note: Sorting these keys lexicographically is not "correct", but it
2447 | // does the right thing for indexes less than 10.
2448 | const keys = Z.filter (k => {
2449 | const values_ = valuesByPath[k];
2450 | return (
2451 | // Keep X, the position at which the violation was observed.
2452 | k === key ||
2453 | // Keep positions whose values are incompatible with the values at X.
2454 | (determineActualTypesStrict ([...values, ...values_])).length === 0
2455 | );
2456 | }, Z.sort (globalThis.Object.keys (valuesByPath)));
2457 |
2458 | return new TypeError (
2459 | `Type-variable constraint violation\n\n${
2460 | underlineTypeVars (
2461 | typeInfo,
2462 | keys.reduce (($valuesByPath, k) => ((
2463 | $valuesByPath[k] = valuesByPath[k],
2464 | $valuesByPath
2465 | )), {})
2466 | )
2467 | }\n${
2468 | keys.reduce (({idx, s}, k) => {
2469 | const values = valuesByPath[k];
2470 | return values.length === 0
2471 | ? {idx, s}
2472 | : {idx: idx + 1,
2473 | s: s + showValuesAndTypes (typeInfo, values, idx + 1)
2474 | + '\n\n'};
2475 | }, {idx: 0, s: ''})
2476 | .s
2477 | }` +
2478 | 'Since there is no type of which all the above values are ' +
2479 | 'members, the type-variable constraint has been violated.\n'
2480 | );
2481 | };
2482 |
2483 | // invalidValue :: (TypeInfo, Integer, PropPath, Any) -> Error
2484 | const invalidValue = (typeInfo, index, propPath, value) => {
2485 | const t = propPath.reduce (
2486 | (t, prop) => t.types[prop],
2487 | typeInfo.types[index]
2488 | );
2489 | return new TypeError (
2490 | t.type === VARIABLE &&
2491 | (determineActualTypesLoose ([value])).length === 0 ?
2492 | `Unrecognized value\n\n${
2493 | underline_ (typeInfo) (formatType6 ([index, ...propPath]))
2494 | }\n${
2495 | showValuesAndTypes (typeInfo, [value], 1)
2496 | }\n\n${
2497 | config.env.length === 0
2498 | ? 'The environment is empty! ' +
2499 | 'Polymorphic functions require a non-empty environment.\n'
2500 | : 'The value at position 1 is not a member of any type in ' +
2501 | 'the environment.\n\n' +
2502 | 'The environment contains the following types:\n\n' +
2503 | Z.foldMap (
2504 | globalThis.String,
2505 | t => ` - ${showTypeWith (typeInfo.types) (t)}\n`,
2506 | config.env
2507 | )
2508 | }` :
2509 | // else
2510 | `Invalid value\n\n${
2511 | underline_ (typeInfo) (formatType6 ([index, ...propPath]))
2512 | }\n${
2513 | showValuesAndTypes (typeInfo, [value], 1)
2514 | }\n\nThe value at position 1 is not a member of ‘${
2515 | show (t)
2516 | }’.\n${
2517 | t.url == null || t.url === ''
2518 | ? ''
2519 | : `\nSee ${
2520 | t.url
2521 | } for information about the ${
2522 | t.name
2523 | } ${
2524 | t.arity > 0 ? 'type constructor' : 'type'
2525 | }.\n`
2526 | }`
2527 | );
2528 | };
2529 |
2530 | // invalidArgumentsLength :: ... -> Error
2531 | //
2532 | // This function is used in `wrapFunctionCond` to ensure that higher-order
2533 | // functions defined via `def` only ever apply a function argument to the
2534 | // correct number of arguments.
2535 | const invalidArgumentsLength = (
2536 | typeInfo, // :: TypeInfo
2537 | index, // :: Integer
2538 | numArgsExpected, // :: Integer
2539 | args // :: Array Any
2540 | ) => (
2541 | new TypeError (
2542 | `‘${
2543 | typeInfo.name
2544 | }’ applied ‘${
2545 | show (typeInfo.types[index])
2546 | }’ to the wrong number of arguments\n\n${
2547 | underline_ (typeInfo)
2548 | (index_ => f => t => propPath => s =>
2549 | index_ === index
2550 | ? t.format (
2551 | s => ' '.repeat (s.length),
2552 | k => k === '$1' ? f : s => ' '.repeat (s.length)
2553 | )
2554 | : ' '.repeat (s.length))
2555 | }\nExpected ${
2556 | numArgs (numArgsExpected)
2557 | } but received ${
2558 | numArgs (args.length)
2559 | }${
2560 | args.length === 0
2561 | ? '.\n'
2562 | : ':\n\n' + Z.foldMap (globalThis.String, x => ` - ${show (x)}\n`, args)
2563 | }`
2564 | )
2565 | );
2566 |
2567 | // assertRight :: Either (() -> Error) a -> a !
2568 | function assertRight(either) {
2569 | if (either.isLeft) throw either.value ();
2570 | return either.value;
2571 | }
2572 |
2573 | // withTypeChecking :: ... -> Function
2574 | function withTypeChecking(
2575 | typeInfo, // :: TypeInfo
2576 | impl // :: Function
2577 | ) {
2578 | const n = typeInfo.types.length - 1;
2579 |
2580 | // wrapFunctionCond :: (TypeVarMap, Integer, a) -> a
2581 | const wrapFunctionCond = (_typeVarMap, index, value) => {
2582 | const expType = typeInfo.types[index];
2583 | if (expType.type !== FUNCTION) return value;
2584 |
2585 | // checkValue :: (TypeVarMap, Integer, String, a) -> Either (() -> Error) TypeVarMap
2586 | const checkValue = (typeVarMap, index, k, x) => {
2587 | const propPath = [k];
2588 | const t = expType.types[k];
2589 | return (
2590 | t.type === VARIABLE ?
2591 | Z.chain (
2592 | typeVarMap => (
2593 | typeVarMap[t.name].types.length === 0
2594 | ? Left (() =>
2595 | typeVarConstraintViolation (
2596 | typeInfo,
2597 | index,
2598 | propPath,
2599 | typeVarMap[t.name].valuesByPath
2600 | )
2601 | )
2602 | : Right (typeVarMap)
2603 | ),
2604 | Right (updateTypeVarMap (typeVarMap,
2605 | t,
2606 | index,
2607 | propPath,
2608 | [x]))
2609 | ) :
2610 | // else
2611 | Z.map (
2612 | r => r.typeVarMap,
2613 | satisfactoryTypes (typeInfo,
2614 | typeVarMap,
2615 | t,
2616 | index,
2617 | propPath,
2618 | [x])
2619 | )
2620 | );
2621 | };
2622 |
2623 | let typeVarMap = _typeVarMap;
2624 | return (...args) => {
2625 | if (args.length !== expType.arity - 1) {
2626 | throw invalidArgumentsLength (typeInfo,
2627 | index,
2628 | expType.arity - 1,
2629 | args);
2630 | }
2631 |
2632 | typeVarMap = assertRight (
2633 | expType.keys
2634 | .slice (0, -1)
2635 | .reduce (
2636 | (either, k, idx) => (
2637 | Z.chain (
2638 | typeVarMap => checkValue (typeVarMap, index, k, args[idx]),
2639 | either
2640 | )
2641 | ),
2642 | Right (typeVarMap)
2643 | )
2644 | );
2645 |
2646 | const output = value.apply (this, args);
2647 | const k = expType.keys[expType.keys.length - 1];
2648 | typeVarMap = assertRight (checkValue (typeVarMap, index, k, output));
2649 | return output;
2650 | };
2651 | };
2652 |
2653 | // wrapNext :: (TypeVarMap, Array Any, Integer) -> (a -> b)
2654 | const wrapNext = (_typeVarMap, _values, index) => (head, ...tail) => {
2655 | if (!config.checkTypes) {
2656 | return impl (head, ...tail);
2657 | }
2658 | const args = [head, ...tail];
2659 | if (args.length !== 1) {
2660 | throw invalidArgumentsCount (typeInfo, index, 1, args);
2661 | }
2662 | let {typeVarMap} = assertRight (
2663 | satisfactoryTypes (typeInfo,
2664 | _typeVarMap,
2665 | typeInfo.types[index],
2666 | index,
2667 | [],
2668 | args)
2669 | );
2670 |
2671 | const values = [..._values, ...args];
2672 | if (index + 1 === n) {
2673 | const value = values.reduce (
2674 | (f, x, idx) => f (wrapFunctionCond (typeVarMap, idx, x)),
2675 | impl
2676 | );
2677 | ({typeVarMap} = assertRight (
2678 | satisfactoryTypes (typeInfo,
2679 | typeVarMap,
2680 | typeInfo.types[n],
2681 | n,
2682 | [],
2683 | [value])
2684 | ));
2685 | return wrapFunctionCond (typeVarMap, n, value);
2686 | } else {
2687 | return wrapNext (typeVarMap, values, index + 1);
2688 | }
2689 | };
2690 |
2691 | const wrapped = typeInfo.types[0].type === NO_ARGUMENTS ?
2692 | (...args) => {
2693 | if (!config.checkTypes) {
2694 | return impl (...args);
2695 | }
2696 | if (args.length !== 0) {
2697 | throw invalidArgumentsCount (typeInfo, 0, 0, args);
2698 | }
2699 | const value = impl ();
2700 | const {typeVarMap} = assertRight (
2701 | satisfactoryTypes (typeInfo,
2702 | {},
2703 | typeInfo.types[n],
2704 | n,
2705 | [],
2706 | [value])
2707 | );
2708 | return wrapFunctionCond (typeVarMap, n, value);
2709 | } :
2710 | wrapNext ({}, [], 0);
2711 |
2712 | wrapped.toString = () => typeSignature (typeInfo);
2713 | if (globalThis.process?.versions?.node != null) {
2714 | const inspect = globalThis.Symbol.for ('nodejs.util.inspect.custom');
2715 | wrapped[inspect] = wrapped.toString;
2716 | }
2717 | /* c8 ignore start */
2718 | if (typeof globalThis.Deno?.customInspect === 'symbol') {
2719 | const inspect = globalThis.Deno.customInspect;
2720 | wrapped[inspect] = wrapped.toString;
2721 | }
2722 | /* c8 ignore stop */
2723 |
2724 | return wrapped;
2725 | }
2726 |
2727 | export const def =
2728 | _def ('def')
2729 | ({})
2730 | ([String,
2731 | StrMap (Array (TypeClass)),
2732 | NonEmpty (Array (Type)),
2733 | AnyFunction,
2734 | AnyFunction])
2735 | (_def);
2736 |
2737 | //. [Buffer]: https://nodejs.org/api/buffer.html#buffer_buffer
2738 | //. [Descending]: v:sanctuary-js/sanctuary-descending
2739 | //. [Either]: v:sanctuary-js/sanctuary-either
2740 | //. [FL:Semigroup]: https://github.com/fantasyland/fantasy-land#semigroup
2741 | //. [HTML element]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element
2742 | //. [Identity]: v:sanctuary-js/sanctuary-identity
2743 | //. [Maybe]: v:sanctuary-js/sanctuary-maybe
2744 | //. [Monoid]: https://github.com/fantasyland/fantasy-land#monoid
2745 | //. [Pair]: v:sanctuary-js/sanctuary-pair
2746 | //. [Setoid]: https://github.com/fantasyland/fantasy-land#setoid
2747 | //. [Unknown]: #Unknown
2748 | //. [`Array`]: #Array
2749 | //. [`Array2`]: #Array2
2750 | //. [`BinaryType`]: #BinaryType
2751 | //. [`Date`]: #Date
2752 | //. [`FiniteNumber`]: #FiniteNumber
2753 | //. [`GlobalRegExp`]: #GlobalRegExp
2754 | //. [`Integer`]: #Integer
2755 | //. [`NonGlobalRegExp`]: #NonGlobalRegExp
2756 | //. [`Number`]: #Number
2757 | //. [`Object.create`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
2758 | //. [`RegExp`]: #RegExp
2759 | //. [`RegexFlags`]: #RegexFlags
2760 | //. [`String`]: #String
2761 | //. [`SyntaxError`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError
2762 | //. [`TypeClass`]: https://github.com/sanctuary-js/sanctuary-type-classes#TypeClass
2763 | //. [`TypeError`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError
2764 | //. [`TypeVariable`]: #TypeVariable
2765 | //. [`UnaryType`]: #UnaryType
2766 | //. [`UnaryTypeVariable`]: #UnaryTypeVariable
2767 | //. [`Unknown`]: #Unknown
2768 | //. [`ValidNumber`]: #ValidNumber
2769 | //. [`config.env`]: #config.env
2770 | //. [`def`]: #def
2771 | //. [arguments]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments
2772 | //. [enumerated types]: https://en.wikipedia.org/wiki/Enumerated_type
2773 | //. [max]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER
2774 | //. [min]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_SAFE_INTEGER
2775 | //. [semigroup]: https://en.wikipedia.org/wiki/Semigroup
2776 | //. [type class]: #type-classes
2777 | //. [type variable]: #TypeVariable
2778 | //. [types]: #types
2779 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sanctuary-def",
3 | "version": "0.22.0",
4 | "description": "Run-time type system for JavaScript",
5 | "license": "MIT",
6 | "repository": {
7 | "type": "git",
8 | "url": "git://github.com/sanctuary-js/sanctuary-def.git"
9 | },
10 | "type": "module",
11 | "exports": {
12 | ".": "./index.js",
13 | "./package.json": "./package.json"
14 | },
15 | "scripts": {
16 | "doctest": "sanctuary-doctest",
17 | "lint": "sanctuary-lint",
18 | "release": "sanctuary-release",
19 | "test": "npm run lint && sanctuary-test && npm run doctest"
20 | },
21 | "engines": {
22 | "node": ">=14.0.0"
23 | },
24 | "dependencies": {
25 | "sanctuary-either": "2.1.0",
26 | "sanctuary-show": "2.0.0",
27 | "sanctuary-type-classes": "12.1.0",
28 | "sanctuary-type-identifiers": "3.0.0"
29 | },
30 | "devDependencies": {
31 | "sanctuary-descending": "2.1.0",
32 | "sanctuary-identity": "2.1.0",
33 | "sanctuary-maybe": "2.1.0",
34 | "sanctuary-pair": "2.1.0",
35 | "sanctuary-scripts": "7.0.x"
36 | },
37 | "files": [
38 | "/LICENSE",
39 | "/README.md",
40 | "/index.js",
41 | "/package.json"
42 | ]
43 | }
44 |
--------------------------------------------------------------------------------
/scripts/prepublish:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euf -o pipefail
3 |
4 | node_modules/.bin/sanctuary-prepublish "$@"
5 |
6 | sed "s/ version = '.*';/ version = '$VERSION';/" index.js >index.js.updated
7 | mv index.js.updated index.js
8 | git add index.js
9 |
--------------------------------------------------------------------------------
/test/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "extends": ["../node_modules/sanctuary-style/eslint.json"],
4 | "parserOptions": {"ecmaVersion": 2022, "sourceType": "module"},
5 | "env": {"node": true},
6 | "rules": {
7 | "max-len": ["off"]
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/test/module.js:
--------------------------------------------------------------------------------
1 | import * as $ from '../index.js';
2 |
3 |
4 | const a = $.TypeVariable ('a');
5 |
6 | $.def
7 | ('I')
8 | ({})
9 | ([a, a])
10 | (x => x)
11 | ($);
12 |
--------------------------------------------------------------------------------