New typescript type derived from literals that generates a compilation error when assigned to simple literal or other types

huangapple go评论69阅读模式
英文:

New typescript type derived from literals that generates a compilation error when assigned to simple literal or other types

问题

以下是您要翻译的内容:

为了避免混淆所有我声明的变量之间的数字类型,我想通过以一种方式输入它们,以便如果我尝试将字面数或其他类型分配给这种新类型,将生成编译错误,以增加额外的安全性层。

您可以将其视为为我的变量分配一个单位,例如“second”和“meter”。它们都源自字面类型“number”,但我不应该能够将类型为second的变量与类型为meter的变量相加。我也不应该能够将类型为second的变量分配给另一个类型为meter的变量。

let length1   =  5 as Meter;
let length2   =  7 as Meter;
let duration1 = 10 as Second;
let duration2 = 11 as Second;

let lengthSum = length1 + length2         // 应将lengthSum的类型设为Meter并给它一个值为12
let durationSum = duration1 + duration2   // 应将durationSum的类型设为Second并给它一个值为21

length1 = duration2                // 应生成一个错误
let foo = length1 + duration1;     // 应生成一个错误

此外,我也希望对字符串执行相同的操作:

let myBookTitle = "Best title ever" as Title;
let myBookAuthor = "John Doe" as Author;

myBookTitle = myBookAuthor // 应生成一个错误

我已经尝试使用以下自定义类型:

type Meter = number & { __type: 'meter' };
type Second = number & { __type: 'second' };

它们适用于大多数情况,但无法处理混合的加法情况:

let length1 = 5 as Meter;
let length2 = 10 as Meter;
let duration1 = 2 as Second;
let duration2 = 3 as Second;

length1 = length2; // 如预期的那样工作
length1 = duration1; // 生成了预期的错误

let length10 = (length1 + length2) as Meter; // 能够工作,但无法自动推断length10类型为“as Meter”
let length11 = (duration1 + duration2) as Second; // 与上述情况相同

let length12 = (length1 + duration2) as Second; // 不应允许这种加法,但它却在没有错误的情况下执行了!!

是否有人知道如何创建一个新类型,以满足我所有的约束?(我知道有一些npm包专门用于处理国际单位制单位,但这不是我要找的。这里使用单位仅作为示例;我将需要处理多种不同类型的类型外部“简单”单位。谢谢。)

英文:

To avoid mixing all my variables typed number with each other, I want to add an additional layer of security by typing them in a way that would generates a compilation error if I try to assign a literal number or another type to this new type.

You can see that as giving a unit to my variables, for instance "second" and "meter". They both are derived from the literal type "number", but I shouldn't be able to add a variable typed second with one typed meter. I shouldn't neither be able to assign a variable of type second to another one of type meter.

let length1   =  5 as Meter;
let length2   =  7 as Meter;
let duration1 = 10 as Second;
let duration2 = 11 as Second;

let lengthSum = length1 + length2         // should type lengthSum as Meter and give it a value of 12
let durationSum = duration1 + duration2   // should type durationSum as Second and give it a value of 21

length1 = duration2                // should generate an error
let foo = length1 + duration1;     // should generate an error

By extension, I would also like to do the same with strings:

let myBookTitle = "Best title ever" as Title;
let myBookAuthor = "John Doe" as Author;

myBookTitle = myBookAuthor // should generate an error

.

I have tried with the following custom types:

type Meter = number & { __type: 'meter' };
type Second = number & { __type: 'second' };

They work for most cases but miss the mixed addition case:

let length1 = 5 as Meter;
let length2 = 10 as Meter;
let duration1 = 2 as Second;
let duration2 = 3 as Second;

length1 = length2; // works as expected
length1 = duration1; // generates an error as expected

let length10 = (length1 + length2) as Meter; // works but is not able to automatically infer the length10 type with the "as Meter"
let length11 = (duration1 + duration2) as Second; //same as above

let length12 = (length1 + duration2) as Second; // Shouldn't allow such addition but does it without any error!!

Would anyone know how to create a new type that covers all my constraints?
(I know there are some npm packages that cover specifically the SI units, but that is not what I am looking for. Units are used here as examples; I will need to handle multiple types of différents types outside "simple" units. Thanks.)

答案1

得分: 1

以下是翻译好的部分:

抱歉,根据您的要求,我将只返回翻译后的部分,不包括代码部分:


不幸的是,根据目前的要求,无法实现。像加法运算符(+)这样的原生JavaScript运算符的TypeScript行为已经嵌入到语言中,您无法覆盖或自定义它。所以,如果您写 x + y,其中 xy 是某种 number 的子类型,你将得到一个普通的 number,从而取消了任何您应用的类似命名类型品牌的操作。

有一个开放的功能请求在 microsoft/TypeScript#42218,允许您实际上合并这些运算符的自定义重载签名,但除非实现了这样的功能,否则无法实现这一点。


眼下只有一些变通方法。您使用的特定变通方法可能超出了问题的范围,但以下是一些可能作为起点有用的可能性:

您无法自定义运算符行为,但您可以自定义函数行为,因此如果需要,您可以将运算符包装在函数中,然后使用这些函数:

function add<T extends number>(t1: T, t2: T): T {
    return t1 + t2 as T;
}

let length10 = add(length1, length2);
// ^? let length10: Meter
let length11 = add(duration1, duration2);
// ^? let length11: Second
let length12 = add(length1, duration2); // error

显然,这不是理想的,因为您需要记住使用 add() 而不是 +

或者,您可以重构以使用包含数字值的类,而不是直接使用数字:

class BrandedNumber<T extends string> {
    constructor(public __type: T, public value: number) { }
    add(other: BrandedNumber<T>) {
        return new BrandedNumber(this.__type, this.value + other.value);
    }
}
const Meter = (value: number) => new BrandedNumber("meter", value);
const Second = (value: number) => new BrandedNumber("second", value);
let length1 = Meter(5);
let length2 = Meter(10);
let duration1 = Second(2);
let duration2 = Second(3);
length1 = length2; // okay
length1 = duration1; // error
let length10 = length1.add(length2);
// ^? let length10: BrandedNumber<"meter">
let length11 = duration1.add(duration2);
// ^? let length11: BrandedNumber<"second">
let length12 = length1.add(duration2); // error

这也不是理想的,特别是如果您关心保留运行时行为(您可能不希望执行包装和解包数字的这种计算密集型处理)。

再次强调,选择的特定变通方法取决于使用情况,不在此问题的范围内;主要问题是,没有运算符重载,需要一种变通方法。

英文:

Unfortunately this is not currently possible as asked. The TypeScript behavior of native JavaScript operators like the addition operator (+) is baked into the language and you can't override or customize it. So if you write x + y where x and y are some subtype of number, you will get a plain number out, undoing any nominal-like type branding you've applied.

There is an open feature request at microsoft/TypeScript#42218 to be able to essentially merge your own overload signatures for these operators, but until and unless such a feature is implemented there's no way to do this.


For now there are only workarounds. The particular workaround you use is probably out of scope for the question, but here are some possibilities that might be useful as a starting point:

You can't customize operator behavior, but you can customize function behavior, so if you want you can wrap operators in functions and then use the functions instead:

function add&lt;T extends number&gt;(t1: T, t2: T): T {
    return t1 + t2 as T;
}

let length10 = add(length1, length2);
//  ^? let length10: Meter
let length11 = add(duration1, duration2);
//  ^? let length11: Second
let length12 = add(length1, duration2); // error

Obviously this isn't ideal, since you need to remember to use add() instead of +.

Or, you could refactor to use classes holding number values instead of the numbers directly:

class BrandedNumber&lt;T extends string&gt; {
    constructor(public __type: T, public value: number) { }
    add(other: BrandedNumber&lt;T&gt;) {
        return new BrandedNumber(this.__type, this.value + other.value);
    }
}
const Meter = (value: number) =&gt; new BrandedNumber(&quot;meter&quot;, value);
const Second = (value: number) =&gt; new BrandedNumber(&quot;second&quot;, value);
let length1 = Meter(5);
let length2 = Meter(10);
let duration1 = Second(2);
let duration2 = Second(3);
length1 = length2; // okay
length1 = duration1; // error
let length10 = length1.add(length2);
//  ^? let length10: BrandedNumber&lt;&quot;meter&quot;&gt;
let length11 = duration1.add(duration2);
//  ^? let length11: BrandedNumber&lt;&quot;second&quot;&gt;
let length12 = length1.add(duration2); // error

which also isn't ideal, especially if you care about preserving the runtime behavior (presumably you wouldn't want to do any computation-intensive processing that wraps and unwraps numbers like this).

Again, the particular workaround one chooses depends on one's use cases and isn't really in scope here; the main point is that without operator overloading, a workaround is necessary.

Playground link to code

huangapple
  • 本文由 发表于 2023年4月10日 20:01:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/75976920.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定