如何在 TypeScript 中创建 2 个不兼容的类数字类型?

How to create 2 incompatible number-like types in TypeScript?

我一直在努力弄清楚如何在 TS 中创建 2 个互不兼容的 number-like/integer 类型。

例如,在下面的代码中,身高和体重都是类似数字的,但是将它们加在一起或等同对待它们的概念是荒谬的,应该是一个错误。

type height = number; // inches
type weight = number; // pounds
var a: height = 68;
var b: weight = operateScale(); // Call to external, non-TS function, which returns a weight.
console.log(a+b); // Should be an error.

有没有办法创建两种类型,它们都是数字,但彼此不兼容?


编辑:如评论部分所述,此行为似乎类似于 haskell 的 newtype 行为。


EDIT2:在用尖头棒戳问题几个小时后,我设法找到了答案,我已将其发布在下面。

所以,在用头撞墙几个小时后,我想出了这个:

class height extends Number {}
class weight extends Number {}

通过子classing Number class,Typescript 允许您创建不同的数字类型。

然后你就可以使用上面指定的变量了。

var a: height = 68;
var b: weight = 184;
console.log(a+b); // Should be an error.

我 运行 遇到的问题是这也是 returns 一个错误:

console.log(a+a); // Should NOT be an error.

TypeScript 中最接近 newtype 的是创建一个新的“标称”类型(TypeScript 没有标称类型,但有 branding 等变通方法)并创建一个值构造函数和一个字段访问器函数,它只在实现中使用类型断言。例如:

interface Height { 
  __brand: "height"
}
function height(inches: number): Height {
  return inches as any;
}
function inches(height: Height): number {
  return height as any;
}

interface Weight { 
  __brand: "weight"
}
function weight(pounds: number): Weight {
  return pounds as any;
}
function pounds(weight: Weight): number {
  return weight as any;
}

const h = height(12); // one foot
const w = weight(2000); // one ton

HeightWeight 类型(抱歉,我不能给新类型起一个小写名称)被编译器视为不同的类型。 height()函数是一个Height值构造函数(接受一个number和return一个Height),inches()函数是它的关联字段accessor(取一个 Height 和 return 一个 number),weight()pounds()Weight 的类似函数。所有这些函数都只是运行时的身份函数。因此 JavaScript 将它们视为带有一些函数调用开销的纯数字,希望通过一个好的编译器将其优化掉,如果您 真的 担心这种开销,您可以自己做断言:

const h = 12 as any as Height;
const w = 2000 as any as Weight;

现在您可以使用不同的命名类型,这样您就不会在需要 Weight 的地方意外使用 Height,反之亦然。但是,就像 newtype 一样,编译器不会将它们视为 number。是的,您可以创建 numberHeightWeight 子类型(通过交集类型),但这可能是一个错误:像 + 这样的算术运算符能够对 number 值,如果 hw 都是 number 的子类型,那么 h + w 不会出错。如果 hw 而不是 number 子类型,那么 h + h 将是一个错误。而且你无法更改它,因为 TypeScript 不允许你更改运算符 the way it does with functions 的类型声明。我更喜欢阻止 h + hh + w 编译,所以 HeightWeight 类型不是 number。相反,让我们创建自己的 add() 函数来实现您想要的行为:

type Dimension = Height | Weight;

function add<D extends Dimension>(a: D, b: D): D {
  return ((a as any) + (b as any)) as any;
}

add() 函数接受 两个 Height 或两个 Weight 参数,并且 return 的值相同类型。实际上,通过上面的方法,仍然可以将 Height | Weight 之类的东西作为 D 传递,所以如果你真的想锁定它,你可以改用重载:

function add(a: Height, b: Height): Height;
function add(a: Weight, b: Weight): Weight;
function add(a: any, b: any): any {
  return a+b;
}

然后,看:

const twoH = add(h, h); // twoH is a Height
const twoW = add(w, w); // twoW is a Weight
const blah = add(h, w); // error, Height and Weight don't mix

所以我们快完成了。对于外部 measureScale() 函数,您只需将 return 类型声明为 Weight:

declare function measureScale(): Weight;

var a = height(68);
var b = measureScale();

并验证预期结果:

console.log(add(a,b)); // err
console.log(add(a,a)); // okay

Playground link to code