Repository: TypeScript
Issue: Tag types
Labels: Discussion

### Problem
- There is no straight way to encode a predicate (some checking) about a value in its type.
### Details

There are situations when a value has to pass some sort of check/validation prior to being used. For example: a min/max functions can only operate on a non-empty array so there must be a check if a given array has any elements. If we pass a plain array that might as well be empty, then we need to account for such case inside the min/max functions, by doing one of the following:
- crashing
- returning undefined
- returning a given default value

This way the calling side has to deal with the consequences of min/max being called yet not being able to deliver.

``` typescript
function min<a>(values: a[], isGreater: (one: a, another: a) => boolean) : a {
   if (values.length < 1) { throw Error('Array is empty.'); }
   // ...
}
var found;
try {
   found = min([]);
} catch (e) {
   found = undefined;
}
```
### Solution

A better idea is to leverage the type system to rule out a possibility of the min function being called with an empty array. In order to do so we might consider so called tag types. 

A **tag type** is a qualifier type that indicates that some predicate about a value it is associated with holds true.

``` typescript

const enum AsNonEmpty {} // a tag type that encodes that a check for being non-empty was passed

function min<a>(values: a[] & AsNonEmpty) : a {
   // only values that are tagged with AsNonEmpty can be passed to this function
   // the type system is responsible for enforcing this constraint
   // a guarantee by the type system that only non-empty array will be passed makes
   // implementation of this function simpler because only one main case needs be considered
   // leaving empty-case situations outside of this function
}

min([]); // <-- compile error, tag is missing, the argument is not known to be non-empty
min([1, 2]); // <-- compile error, tag is missing again, the argument is not know to be non-empty
```

it's up to the developer in what circumstances an array gets its AsNonEmpty tag, which can be something like:

``` typescript
// guarntee is given by the server, so we always trust the data that comes from it
interface GuaranteedByServer {
    values: number[] & AsNonEmpty;
}

```

Also tags can be assigned at runtime:

``` typescript

function asNonEmpty(values: a[]) : (a[] & AsNonEmpty) | void {
    return values.length > 0 ? <a[] & AsNonEmpty> : undefined;
}

function isVoid<a>(value: a | void) : value is void {
  return value == null;
}

var values = asNonEmpty(Math.random() > 0.5 ? [1, 2, 3] : []);
if (isVoid(values)) {
   // there are no  elements in the array, so we can't call min
   var niceTry = min(values); // <-- compile error; 
} else {
    var found = min(values); // <-- ok, tag is there, safe to call
}

```

As was shown in the current version (1.6) an empty const enum type can be used as a marker type (AsNonEmpty in the above example), because
- enums might not have any members and yet be different from the empty type
- enums are branded (not assignable to one another)

However enums have their limitations:
- enum is assignable by numbers
- enum cannot hold a type parameter
- enum cannot have members

A few more examples of what tag type can encode:
- `string & AsTrimmed & AsLowerCased & AsAtLeast3CharLong`
- `number & AsNonNegative & AsEven`
- `date & AsInWinter & AsFirstDayOfMonth`

Custom types can also be augmented with tags. This is especially useful when the types are defined outside of the project and developers can't alter them.
- `User & AsHavingClearance`

**ALSO NOTE**: In a way tag types are similar to boolean properties (flags), BUT they get type-erased and carry no rutime overhead whatsoever being a good example of a zero-cost abstraction.

**UPDATED:**

Also tag types can be used as units of measure in a way:
- `string & AsEmail`, `string & AsFirstName`:

``` typescript

var email = <string & AsEmail> 'aleksey.bykov@yahoo.com';
var firstName = <string & AsFirstName> 'Aleksey';
firstName = email; // <-- compile error
```
- `number & In<Mhz>`, `number & In<Px>`:

``` typescript

var freq = <number & In<Mhz>> 12000;
var width =   <number & In<Px>> 768;
freq = width; // <-- compile error

function divide<a, b>(left: number & In<a>, right: number & In<b> & AsNonZero) : number & In<Ratio<a, b>> {
     return <number & In<Ratio<a, b>>> left  / right;
}

var ratio = divide(freq, width); // <-- number & In<Ratio<Mhz, Px>>

```

```
```