A trademark represents a generative nominal type. A trademark has two facets:
To make brands unforgeable, the two facets of a trademark are represented by different ECMAScript objects, linked to each other under the scenes. Given the brander one can readily create a guard. On the other hand, one cannot obtain the brander given just the guard of a trademark. Thus the brander of a trademark is a capability.
Some of the API relating to trademarks also consists of static global methods. These are gathered into a Type object, which is a collection of static methods analogous to Math or JSON. Type is imported into scope using the module mechanism.
The branding facet of a trademark is an object with the following frozen methods. Here specimen is an arbitrary ECMAScript value and guard is a guard.
test(specimen)true or false indicating whether specimen has been branded with this trademark.validate(specimen)test(specimen) returns true, return specimen. Otherwise throw a TypeError.brand(specimen)brandPrototype(specimen)append(guard)isGuard(guard)true. Otherwise return false.setGuard(guard) (optional, not needed if the facilities for specifying that an object is a guard at creation time are sufficient)
Branders are created by Type.make below.
The guarding facet of a trademark is an object with a [[Brand]] internal property. There are two ways to set this property to a brander b on a guard g:
makeGuard(g) (not needed if the above mechanism is adequate)Once g‘s [[Brand]] is set, it cannot be deleted or changed. Furthermore, user code cannot directly read the [[Brand]] internal property.
Guards are typically used for constraining the values of variables, fields, or parameters as part of guards:
let x :: guard = value; function f(a :: guard1, b :: guard2, c) :: resultGuard {...}
One can also invoke a guard manually using Type methods. These methods are on Type instead of directly on the guard to make it easy to make any object into an guard without polluting its property name space. In particular, Object, Number, String, etc. can serve as built-in guards.
The ~ and | operators have special behavior when their operands are guards. When given guards as both operands, the | operator invokes Type.union on the operands. When given a guard as the operand, the ~ operator invokes Type.union on the guard with Null and Void. These let one conveniently make guard annotations such as:
let x :: Number | Boolean = value; // Either a Number of a Boolean let y :: ~Number = value; // Either a Number or null or undefined
The Type global object is a collection of static methods for working with branders and guards. It provides the following methods:
make()make(guard1, guard2, ...)make to create a new brander b followed by a series of b.append calls to append all of the given guards to b. This will make the new brander the union of the guards’ types.union(guard1, guard2, ...)make above, but returns the guard, not the brander. Other than containing the [[Brand]] internal property, the returned guard object is like an object created by {}. There is no way to access the brander.test(guard, specimen)true or false indicating whether specimen has been branded with guard‘s trademark. Throw an error if guard is not a guard. test invokes guard.[[Brand]].test(specimen).validate(guard, specimen)test(guard, specimen) returns true, return specimen. Otherwise throw a TypeError. validate invokes guard.[[Brand]].validate(specimen).There intentionally is no way to make complement or difference types because doing so would violate monotonicity: if specimen s is in t1 but not t2, then s will be in t1 - t2. However, it will disappear from t1 - t2 if someone later brands s with t2.
We seed the trademark graph by providing guards for built-in types:
Null accepts nullVoid accepts undefinedNumber accepts all primitive numbers, including infinities and NaNInteger accepts all primitive numbers whose values are finite integers, regardless of magnitude. Integer includes both +0 and -0.Boolean accepts the primitives true and falseString accepts all primitive stringsType accepts all guards (i.e. objects directly containing a [[Brand]] internal property)Object accepts all non-primitive objects. Note that null and undefined are not objects.Any accepts everythingBecause guards introduce no extra methods, these uses do not conflict with these objects’ other APIs.
setGuard?appendBrander method on a brander that takes a brander instead of a guard argument?brand, brandPrototype, or append? If so, do it by piggybacking on Object.freeze (i.e. brand, brandPrototype, and append would throw an error if the brander is frozen) or by introducing a freeze method on the brander? In either case, knowing that a brander is frozen might make some code patterns easier to understand and analyze. Note that, just like for all other objects, freezing is shallow, so even a frozen brander can acquire new branded instances by having earlier appending a different brander or by deriving from a prototype-branded prototype.test and validate will never run user code. This guarantees monotonicity — there is no way to revoke a brand. Is this desirable, or do we want to relax this restriction and add a way to write potentially problematic user-specified guards?