如何在 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
Height
和 Weight
类型(抱歉,我不能给新类型起一个小写名称)被编译器视为不同的类型。 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
。是的,您可以创建 number
的 Height
和 Weight
子类型(通过交集类型),但这可能是一个错误:像 +
这样的算术运算符能够对 number
值,如果 h
和 w
都是 number
的子类型,那么 h + w
不会出错。如果 h
和 w
是 而不是 number
的 子类型,那么 h + h
将是一个错误。而且你无法更改它,因为 TypeScript 不允许你更改运算符 the way it does with functions 的类型声明。我更喜欢阻止 h + h
和 h + w
编译,所以 Height
和 Weight
类型不是 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
我一直在努力弄清楚如何在 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
Height
和 Weight
类型(抱歉,我不能给新类型起一个小写名称)被编译器视为不同的类型。 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
。是的,您可以创建 number
的 Height
和 Weight
子类型(通过交集类型),但这可能是一个错误:像 +
这样的算术运算符能够对 number
值,如果 h
和 w
都是 number
的子类型,那么 h + w
不会出错。如果 h
和 w
是 而不是 number
的 子类型,那么 h + h
将是一个错误。而且你无法更改它,因为 TypeScript 不允许你更改运算符 the way it does with functions 的类型声明。我更喜欢阻止 h + h
和 h + w
编译,所以 Height
和 Weight
类型不是 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