【问题标题】:How to create 2 incompatible number-like types in TypeScript?如何在 TypeScript 中创建 2 个不兼容的类数字类型?
【发布时间】:2018-06-11 19:02:00
【问题描述】:

我一直在试图弄清楚如何在 TS 中创建 2 种相互不兼容的类数/整数类型。

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

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.

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


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


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

【问题讨论】:

  • 在 Haskell 中,这将是 newtype 而不是 类型别名
  • @JörgWMittag,是的,这看起来就像我想要的 TS。
  • 谷歌搜索“newtype typescript”会产生一些库。
  • 现在看来这是不可能的。 GitHub issue
  • @Gerrit0 我刚刚完成了。答案如下。

标签: typescript typing


【解决方案1】:

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

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

通过继承 Number 类,Typescript 允许您创建不同的数字类型。

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

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

我遇到的问题是这也返回错误:

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

【讨论】:

    【解决方案2】:

    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 并返回 Height),inches() 函数是其关联的字段访问器(接受 Height 并返回 number)和weight()pounds()Weight 的类似函数。所有这些函数都只是运行时的标识函数。因此 JavaScript 将它们视为纯数字,并带有一些函数调用开销,希望通过一个好的编译器优化掉,如果你真的担心这个开销,你可以自己做断言:

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

    现在您可以使用不同的命名类型,这样您就不会在需要Weight 的地方意外使用Height,反之亦然。但是,就像newtype 一样,编译器不会将它们视为number。是的,您可以将HeightWeight 设为number 的子类型(通过交集类型),但这可能是一个错误:像+ 这样的算术运算符能够对number 值进行操作,如果@987654347 @ 和wnumber 的子类型,那么h + w 不会出错。如果hw 不是number 的子类型,那么h + h 将是一个错误。而且您无法更改它,因为 TypeScript 不允许您更改运算符 the way it does with functions 的类型声明。我更喜欢阻止h + hh + w 编译,所以HeightWeight 类型不是numbers。相反,让我们创建自己的 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 参数,并返回相同类型的值。实际上,通过上述方法,仍然可以将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() 函数,您只需将返回类型声明为 Weight

    declare function measureScale(): Weight;
    
    var a = height(68);
    var b = measureScale();
    

    并验证预期的结果:

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

    希望有所帮助;祝你好运!

    【讨论】:

      猜你喜欢
      • 2020-12-11
      • 2022-01-22
      • 1970-01-01
      • 2020-06-30
      • 2019-09-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多