question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

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.

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.


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:

// 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:


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:

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

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>>

Issue Analytics

  • State:open
  • Created 8 years ago
  • Reactions:46
  • Comments:55 (8 by maintainers)

github_iconTop GitHub Comments

20reactions
SimonMeskenscommented, Jun 28, 2018

@agos the problem with that solution is that it’s not type safe. Symbols are the only nominal type, so you need a solution that uses them. That much is clear.

Here’s the sample adapted to be type-safe:

// Using namespace here simply to show that external files 
// should NOT have access to OpaqueTagSymbol or OpaqueTag. 
// Put this in its own module, without the namespace
namespace Tag {
    declare const OpaqueTagSymbol: unique symbol;
    declare class OpaqueTag<S extends symbol> {
        private [OpaqueTagSymbol]: S;
    }

    export type Opaque<T, S extends symbol> = T & OpaqueTag<S>;
}

// Simple alias, if in a module, same as:
// import { Opaque } from "tagmodule";
type Opaque<T, S extends symbol> = Tag.Opaque<T, S>;

// Since these are ghost symbols, you probably don't want to export them
// Create actual symbols if you do
declare const UserUUIDSymbol: unique symbol;
declare const TransactionId: unique symbol;

type UserUUID = Tag.Opaque<string, typeof UserUUIDSymbol>;
type TransactionId = Opaque<string, typeof TransactionId>;

const a: UserUUID = '...' as UserUUID; // assigning a string literal requires a cast
const b: TransactionId = a // errors
13reactions
mhegazycommented, May 18, 2018

We have talked about this one a few times recently. @weswigham volunteered to write up a proposal.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Types of Tags in HTML - eduCBA
Top 3 Types of Tags in HTML · 1. Paired and Unpaired Tags · 2. Self-Closing Tags · 3. Utility-Based Tags.
Read more >
The Best Guide to HTML Tags - Simplilearn
Formatting Tags · Emphasis tag · Bold Tag · Italic Tag · Underline Tag.
Read more >
HTML Element Reference - By Category - W3Schools
Tag, Description. <!DOCTYPE>, Defines the document type. <html>, Defines an HTML document. <head>, Contains metadata/information for the document.
Read more >
Tag Types - ThingLink
1. Text & media tag · 2. Add content from website. · 3. Tour tag. · 4. Text label tag. · 5. Poll...
Read more >
Types of Tags
Custom tags communicate with each other through shared objects. There are two types of shared objects: public and private. In the following example,...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found