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.

Suggestion: Units of measure

See original GitHub issue

This feature request is similar to units of measure in F#.

For example:

const metres  = 125<m>;
const seconds = 2<s>;
let speed: number<m/s>;

speed = metres / seconds;          // valid
speed = metres / seconds / 10<s>;  // error -- cannot convert m/s**2 to m/s

(Moved from work item 1715 on Codeplex.)

Proposal

Last Updated: 2016-06-09 Copied from: https://github.com/dsherret/Units-of-Measure-Proposal-for-TypeScript


Overview

Units of measure is a useful F# feature that provides the optional ability to create tighter constraints on numbers.

TypeScript could benefit from a similar feature that would add zero runtime overhead, increase type constraints, and help decrease programmer error when doing mathematical calculations that involve units. The feature should prefer explicity.

Defining Units of Measure

Units of measure should probably use syntax similar to type aliases (#957). More discussion is needed on this, but for the purpose of this document it will use the following syntax:

type measure <name> [ = measure expression ];

The optional measure expression part can be used to define a new measures in terms of previously defined measures.

Example Definitions

type measure m;
type measure s;
type measure a = m / s**2;

Units of measure can be defined in any order. For example, a in the example above could have been defined before m or s.

Circular Definitions

Circular definitions are NOT allowed. For example:

type measure a = b;
type measure b = a; // error

Use with Number

Units of measure can be defined on a number type in any of the following ways:

type measure m;

// 1. Explicitly
let distance: number<m> = 12<m>;
// 2. Implictly
let distance = 12<m>;
// 3. Using Number class
let distance = new Number(10)<s>;

TODO: Maybe we shouldn’t use the <m> syntax because it might conflict with jsx files.

Detailed Full Example

type measure m;
type measure s;
type measure a = m / s**2;

let acceleration = 12<a>,
    time         = 10<s>;

let distance = 1/2 * acceleration * (time ** 2); // valid -- implicitly typed to number<m>
let avgSpeed = distance / time;                  // valid -- implicitly typed to number<m/s>

time += 5<s>;         // valid
time += 5;            // error -- cannot convert number to number<s>
time += distance;     // error -- cannot convert number<m> to number<s>

// converting to another unit requires asserting to number then the measure
time += (distance as number)<s>; // valid

acceleration += 12<m / s**2>;         // valid
acceleration += 10<a>;                // valid
acceleration += 12<m / s**2> * 10<s>; // error -- cannot convert number<m/s> to number<a>

Use With Non-Unit of Measure Number Types

Sometimes previously written code or external libraries will return number types without a unit of measure. In these cases, it is useful to allow the programmer to specify the unit like so:

type measure s;

let time = 3<s>;

time += MyOldLibrary.getSeconds();    // error -- type 'number' is not assignable to type 'number<s>'
time += MyOldLibrary.getSeconds()<s>; // valid

Dimensionless Unit

A dimensionless unit is a unit of measure defined as number<1>.

let ratio = 10<s> / 20<s>; // implicitly typed to number<1>
let time: number<s>;

time = 2<s> * ratio;         // valid
time = time / ratio;         // valid
time = (ratio as number)<s>; // valid
time = 2<s> + ratio;         // error, cannot assign number<1> to number<s>
time = ratio;                // error, cannot assign number<1> to number<s>
time = ratio<s>;             // error, cannot assert number<1> to number<s>

Scope

Works the same way as type.

External and Internal Modules

Also works the same way as type.

In addition, if an external library has a definition for meters and another external library has a definition for meters then they should be able to be linked together by doing:

import {m as mathLibraryMeterType} from "my-math-library";
import {m as mathOtherLibraryMeterType} from "my-other-math-library";

type measure m = mathLibraryMeterType | mathOtherLibraryMeterType;

TODO: The above needs more thought though.

Definition File

Units of measure can be defined in TypeScript definition files ( .d.ts) and can be used by any file that references it. Defining units of measure in a definition file is done just the same as defining one in a .ts file.

Compilation

The units of measure feature will not create any runtime overhead. For example:

type measure cm;
type measure m;

let metersToCentimeters = 100<cm / m>;
let length: number<cm> = 20<m> * metersToCentimeters;

Compiles to the following JavaScript:

var metersToCentimeters = 100;
var length = 20 * metersToCentimeters;

Math Library

Units of measure should work well with the current existing Math object.

Some examples:

Math.min(0<s>, 4<m>); // error, cannot mix number<s> with number<m> -- todo: How would this constraint be defined?

let volume = Math.pow(2<m>, 3)<m**3>;
let length = Math.sqrt(4<m^2>)<m>;

Issue Analytics

  • State:open
  • Created 9 years ago
  • Reactions:88
  • Comments:57 (28 by maintainers)

github_iconTop GitHub Comments

12reactions
mindbravecommented, Sep 13, 2018

I have updated it a bit to be more robust and easy to use and published it as an open source lib. I hope you will like it!

Here’s a link: https://github.com/mindbrave/uom-ts

6reactions
mindbravecommented, Sep 6, 2018

@aleksey-bykov I made this (and it works), but it requires your operations to base on my mul and div operations. Also it supports exponents in range <-4, 4> only, but you can extend it. Examples are at the bottom. It has NO runtime overhead. What do you think?

type Exponent = -4 | -3 | -2 | -1 | 0 | 1 | 2 | 3 | 4;

type NegativeExponent<T extends Exponent> = (
    T extends -4 ? 4 :
    T extends -3 ? 3 :
    T extends -2 ? 2 :
    T extends -1 ? 1 :
    T extends 0 ? 0 :
    T extends 1 ? -1 :
    T extends 2 ? -2 :
    T extends 3 ? -3 :
    T extends 4 ? -4 :
    never
);
type SumExponents<A extends Exponent, B extends Exponent> = (
    A extends -4 ? (
        B extends 0 ? -4 :
        B extends 1 ? -3 :
        B extends 2 ? -2 :
        B extends 3 ? -1 :
        B extends 4 ? 0 :
        never
    ) :
    A extends -3 ? (
        B extends -1 ? -4 :
        B extends 0 ? -3 :
        B extends 1 ? -2 :
        B extends 2 ? -1 :
        B extends 3 ? 0 :
        B extends 4 ? 1 :
        never
    ) :
    A extends -2 ? (
        B extends -2 ? -4 :
        B extends -1 ? -3 :
        B extends 0 ? -2 :
        B extends 1 ? -1 :
        B extends 2 ? 0 :
        B extends 3 ? 1 :
        B extends 4 ? 2 :
        never
    ) :
    A extends -1 ? (
        B extends -3 ? -4 :
        B extends -2 ? -3 :
        B extends -1 ? -2 :
        B extends 0 ? -1 :
        B extends 1 ? 0 :
        B extends 2 ? 1 :
        B extends 3 ? 2 :
        B extends 4 ? 3 :
        never
    ) :
    A extends 0 ? (
        B extends -4 ? -4 :
        B extends -3 ? -3 :
        B extends -2 ? -2 :
        B extends -1 ? -1 :
        B extends 0 ? 0 :
        B extends 1 ? 1 :
        B extends 2 ? 2 :
        B extends 3 ? 3 :
        B extends 4 ? 4 :
        never
    ) :
    A extends 1 ? (
        B extends -4 ? -3 :
        B extends -3 ? -2 :
        B extends -2 ? -1 :
        B extends -1 ? 0 :
        B extends 0 ? 1 :
        B extends 1 ? 2 :
        B extends 2 ? 3 :
        B extends 3 ? 4 :
        never
    ) :
    A extends 2 ? (
        B extends -4 ? -2 :
        B extends -3 ? -1 :
        B extends -2 ? 0 :
        B extends -1 ? 1 :
        B extends 0 ? 2 :
        B extends 1 ? 3 :
        B extends 2 ? 4 :
        never
    ) :
    A extends 3 ? (
        B extends -4 ? -1 :
        B extends -3 ? 0 :
        B extends -2 ? 1 :
        B extends -1 ? 2 :
        B extends 0 ? 3 :
        B extends 1 ? 4 :
        never
    ) :
    A extends 4 ? (
        B extends -4 ? 0 :
        B extends -3 ? 1 :
        B extends -2 ? 2 :
        B extends -1 ? 3 :
        B extends 0 ? 4 :
        never
    ) :
    never
);

type Unit = number & {
    s: Exponent,
    m: Exponent,
    kg: Exponent,
};

// basic unit types
type Seconds = number & {
    s: 1,
    m: 0,
    kg: 0,
};
type Meters = number & {
    s: 0,
    m: 1,
    kg: 0,
};
type Kg = number & {
    s: 0,
    m: 0,
    kg: 1,
};

// unit operations
const add = <T extends Unit>(a: T, b: T) => (a + b) as T;
const sub = <T extends Unit>(a: T, b: T) => (a - b) as T;

type MultiplyUnits<A extends Unit, B extends Unit> = number & {
    s: SumExponents<A["s"], B["s"]>,
    m: SumExponents<A["m"], B["m"]>,
    kg: SumExponents<A["kg"], B["kg"]>,
};

type DivideUnits<A extends Unit, B extends Unit> = number & {
    s: SumExponents<A["s"], NegativeExponent<B["s"]>>,
    m: SumExponents<A["m"], NegativeExponent<B["m"]>>,
    kg: SumExponents<A["kg"], NegativeExponent<B["kg"]>>,
};

const mul = <A extends Unit, B extends Unit>(a: A, b: B): MultiplyUnits<A, B> => (a * b) as MultiplyUnits<A, B>;
const div = <A extends Unit, B extends Unit>(a: A, b: B): DivideUnits<A, B> => (a / b) as DivideUnits<A, B>;
const pow = <A extends Unit>(a: A): MultiplyUnits<A, A> => mul(a, a);

// # examples of usage #

// custom unit types
type MetersPerSecond = number & {
    s: -1,
    m: 1,
    kg: 0,
};
type SquaredMeters = number & {
    s: 0,
    m: 2,
    kg: 0,
};
type Newtons = number & {
    s: -2,
    m: 1,
    kg: 1,
};

const speedToDistance = (speed: MetersPerSecond, time: Seconds): Meters => mul(speed, time);
const calculateSpeed = (distance: Meters, time: Seconds): MetersPerSecond => div(distance, time);
const rectangleArea = (width: Meters, height: Meters): SquaredMeters => mul(width, height);

type Vec2<T extends number> = [T, T];

const addVec2 = <T extends Unit>(v1: Vec2<T>, v2: Vec2<T>): Vec2<T> => [add(v1[0], v2[0]), add(v1[1], v2[1])];
const scaleVec2 = <U extends  Unit, T extends Unit>(scale: U, v: Vec2<T>): Vec2<MultiplyUnits<T, U>> => [mul(v[0], scale), mul(v[1], scale)];
const divVec2 = <U extends  Unit, T extends Unit>(factor: U, v: Vec2<T>): Vec2<DivideUnits<T, U>> => [div(v[0], factor), div(v[1], factor)];

type PhysicalBody = {
    velocity: Vec2<MetersPerSecond>,
    mass: Kg
};

// error below because you cant add speed vector to acceleration vector
const applyForceError = (force: Vec2<Newtons>, duration: Seconds, body: PhysicalBody): PhysicalBody => ({
    ...body,
    velocity: addVec2(body.velocity, divVec2(body.mass, force))
});

// this one works because Newtons multiplied by Kilograms and Seconds equals Meters per Seconds, which is body velocity
const applyForce = (force: Vec2<Newtons>, duration: Seconds, body: PhysicalBody): PhysicalBody => ({
    ...body,
    velocity: addVec2(body.velocity, scaleVec2(duration, divVec2(body.mass, force)))
});
Read more comments on GitHub >

github_iconTop Results From Across the Web

Recommendations on Measurement Units – Why and How ...
recommends using an unambiguous terminology of 'measurement units', for daily patient care and scientific publications.
Read more >
Annex II (Informative) Units of Measure: Code elements listed ...
The code elements for units of packaging are specified in UN/ECE Recommendation No. 21 (Codes for types of cargo, packages and packaging materials)....
Read more >
Easy unit conversion by way of "Measurements"
This construct makes it very easy to work with varying units, like converting between units of Mass, Power, Speed, etc. You can see...
Read more >
[Suggestion] Add option to change the units of measurement ...
[Suggestion] Add option to change the units of measurement from metric to imperial/US units ... It would be nice to have US units/imperial...
Read more >
Representing Units of Measure (For Use with Numerical ...
This recommendation may result in errors either by the information system or the human reading the result. Some abbreviations used in UCUM are...
Read more >

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