├── .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 | --------------------------------------------------------------------------------