在TypeScript中表示Adobe的类似`UnitValue`

时间:2017-09-10 21:28:58

标签: typescript types numbers units-of-measurement typing

Adob​​e的ExtendScript采用UnitValue类型,代表屏幕上的距离。它是一种与Number类型相似但不同的类型。

  1. 每个UnitValue对象都带有type字符串,例如"cm""ft"
  2. 携带不同UnitValue值的type个对象之间的算术运算涉及隐式强制; UnitValue对象与普通Number之间的算术运算按原样发生,返回UnitValue
  3. 每个UnitValue对象都带有许多杂项字段; UnitValue原型实现了一堆方法。
  4. UnitValue对象是从构造函数构建的 - var x = UnitValue(4, "cm")
  5. 我怎样才能在TypeScript中最好地表现这一点?

2 个答案:

答案 0 :(得分:0)

实现它的一种方法 - 我选择厘米作为基本单位,但你可以使用别的东西。它使用实例方法进行算术运算,如上所述,您不能在TypeScript中重载运算符。我选择维护调用方法的实例的类型(单位)而不是参数。

请参阅代码中的注释以获取解释,但请询问您是否有任何疑问。

export interface IUnitValue {
    add(a: IUnitValue): IUnitValue;
    subtract(a: IUnitValue): IUnitValue;
    toString(): string;
}

export type Unit = "cm" | "ft";

// UnitValue is a factory function that knows how to construct different
// types of UnitValueClass
export const UnitValue = (value: number, unit: Unit): IUnitValue => {
    switch (unit) {
        case "cm":
            return new UnitValueClass(value, 1, unit);
        case "ft":
            return new UnitValueClass(value, 30, unit);
    }
    throw new Error(`Unrecognised unit ${unit}`);
};

export class UnitValueClass implements IUnitValue {
    private value: number;
    private cmPerUnit: number;
    private type: Unit;

    constructor(value: number, cmPerUnit: number, unit: Unit) {
        this.value = value;
        this.cmPerUnit = cmPerUnit;
        this.type = unit;
    }

    // Return the wrapped value converted to centimeters
    private toCm(): number {
        return this.value * this.cmPerUnit;
    }

    // When adding, convert both operands to centimeters, then convert the result
    // to the correct type and return a new UnitValue
    add(a: this): IUnitValue {
        return UnitValue((this.toCm() + a.toCm()) / this.cmPerUnit, this.type);
    }

    // Same logic as adding
    subtract(a: this): IUnitValue {
        return UnitValue((this.toCm() - a.toCm()) / this.cmPerUnit, this.type);
    }

    // Make it look pretty
    toString() {
        return `${this.value} ${this.type}`;
    }
}

像这样使用:

const a = UnitValue(45, "cm");
const b = UnitValue(1, "ft");

console.log(a.toString());            // 45 cm
console.log(b.toString());            // 1 ft
console.log(b.add(a).toString());     // 2.5 ft
console.log(a.subtract(b).toString());// 15 cm

答案 1 :(得分:0)

技术上可以在TypeScript中使用泛型和文字类型实现单位:

// union of all possible unit types
type UnitType = 'cm' | 'm';

interface UnitConversion<From extends UnitType, To extends UnitType> {
    from: From;
    to: To;
    convert(value: UnitValue<From>): UnitValue<To>;
}

function conversion<From extends UnitType, To extends UnitType>(
    from: From, to: To, convert: (value: UnitValue<From>) => UnitValue<To>
): UnitConversion<From, To> {
    return { from, to, convert };
}

function identity<T extends UnitType>(t: T): UnitConversion<T, T> {
    return { from: t, to: t, convert: v => v };
}

// conversion table for each pair of unit types
const IMPLICIT_CONVERSIONS = {
    'cm': {
        'cm': identity('cm'),
        'm': conversion('cm', 'm', v => new UnitValue(v.value * 0.1, 'm')),
    },
    'm': {
        'cm': conversion('m', 'm', v => new UnitValue(v.value * 10, 'cm')),
        'm': identity('m'),
    },
};
type ImplicitConversions<
    Left extends UnitType,
    Right extends UnitType
> = (typeof IMPLICIT_CONVERSIONS)[Left][Right]['to'];

function convert(conversion: UnitConversion<any, any>, value: UnitValue<any>) {
    return value.type === conversion.to ? value : conversion.convert(value);
}

type UnitPair<T extends UnitType> = {
    left: UnitValue<T>;
    right: UnitValue<T>;
};

function convertToCommonType<Left extends UnitType, Right extends UnitType>(
    left: UnitValue<Left>,
    right: UnitValue<Right>
): UnitPair<ImplicitConversions<Left, Right>> {
    const conversion = IMPLICIT_CONVERSIONS[left.type][right.type];
    return { left: convert(conversion, left), right: convert(conversion, right) };
}

class UnitValue<Type extends UnitType> {
    constructor(
        readonly value: number,
        readonly type: Type,
    ) { }

    /** Type-safe unit addition */
    add<T extends UnitType>(value: UnitValue<T>): UnitValue<ImplicitConversions<Type, T>> {
        const { left, right } = convertToCommonType(this, value);
        return new UnitValue(left.value + right.value, left.type);
    }
}

然后像这样使用它:

const common = convertToCommonType(
  new UnitValue(3, 'cm'),
  new UnitValue(10, 'm')
);
// => result type: UnitValue<'m'>

const z = new UnitValue(4, 'cm').add(new UnitValue(5, 'm'));
// => result type: UnitValue<'m'>

然而,可以说这引入了太多的复杂性。