TypeScript 中具有类型保护和交集类型的新类型模式
Newtype pattern in TypeScript with type guard and intersection types
我正在阅读这篇关于 TS 中的新类型模式的article。
作者展示了使用交集类型和假框类型之间的区别。后者是您主要在所有实现中找到的那个(例如参见 fp-ts 和 lib newtype-ts),因为缺少 'Intersection types':
部分末尾描述的不变量
More importantly, since TypeScript is structurally typed we can pass values of our newtype anywhere the base type is expected. This can be very nice in general, as we can use the type in all the utility functions we might have for original, but also could be quite undesired if we want to maintain certain invariants of the value:
type SortedArray<T> =
T[] & { readonly __tag: unique symbol };
function sort<T>(array: T[]): SortedArray<T> {
return [...array].sort() as SortedArray<T>;
}
const sorted = sort([3, 7, 1]);
// no error here, but should be:
const notSortedAnymore = sorted.concat(2);
我自己在 TS 4.4 中实现了这个例子,我可能遗漏了一些东西,因为 notSortedAnymore
的类型显然不是 SortedArray
而是 number[]
.
对我来说,这似乎不是问题,因为类型系统不会被 concat
方法的使用所误导。我有一个 SortedArray,我连接数字 2,不可能推断它仍然是一个 SortedArray,所以它给了我一个数字数组。
考虑到在 TS 中,对这种模式使用交集类型可以实现更简单(甚至更清晰)的实现(=您可以避免使用 lift
函数来应用简单的concat
之类的函数),为什么通常使用假框类型而不是交集类型来实现 newtype 模式? 和 我在这里缺少什么?
非常感谢
最佳
I may be missing something since the type of notSortedAnymore
is clearly not a SortedArray
but a number[]
.
这不仅仅是 TS 4.4 的事情,我也尝试过旧版本的 TypeScript,结果没有什么不同。
我认为示例中的方法选择不当(或错误?)。相反,它应该是
type SortedArray<T> = T[] & { readonly __tag: unique symbol };
function sort<T>(array: T[]): SortedArray<T> {
return [...array].sort() as SortedArray<T>;
}
const sorted = sort([3, 7, 1]);
// no error here, but should be:
sorted.push(2);
现在 sorted
仍然是 SortedArray<number>
,但它的元素没有排序。结构类型的问题在于它允许调用在原始类型上定义的所有方法,无论它们是否对新类型有效。 slice
可以,push
、pop
或 reverse
不行。
而且不限于方法,还可以定义函数,如
function shuffle<T extends number[]>(arr: T): T {
arr[0] = 4; // chosen by fair dice roll :-)
return arr;
}
并像
一样使用它
const sorted = sort([3, 7, 1]);
const shuffled = shuffle(sorted);
其中 shuffled
将被推断为 SortedArray<number>
。
我正在阅读这篇关于 TS 中的新类型模式的article。
作者展示了使用交集类型和假框类型之间的区别。后者是您主要在所有实现中找到的那个(例如参见 fp-ts 和 lib newtype-ts),因为缺少 'Intersection types':
部分末尾描述的不变量More importantly, since TypeScript is structurally typed we can pass values of our newtype anywhere the base type is expected. This can be very nice in general, as we can use the type in all the utility functions we might have for original, but also could be quite undesired if we want to maintain certain invariants of the value:
type SortedArray<T> =
T[] & { readonly __tag: unique symbol };
function sort<T>(array: T[]): SortedArray<T> {
return [...array].sort() as SortedArray<T>;
}
const sorted = sort([3, 7, 1]);
// no error here, but should be:
const notSortedAnymore = sorted.concat(2);
我自己在 TS 4.4 中实现了这个例子,我可能遗漏了一些东西,因为 notSortedAnymore
的类型显然不是 SortedArray
而是 number[]
.
对我来说,这似乎不是问题,因为类型系统不会被 concat
方法的使用所误导。我有一个 SortedArray,我连接数字 2,不可能推断它仍然是一个 SortedArray,所以它给了我一个数字数组。
考虑到在 TS 中,对这种模式使用交集类型可以实现更简单(甚至更清晰)的实现(=您可以避免使用 lift
函数来应用简单的concat
之类的函数),为什么通常使用假框类型而不是交集类型来实现 newtype 模式? 和 我在这里缺少什么?
非常感谢
最佳
I may be missing something since the type of
notSortedAnymore
is clearly not aSortedArray
but anumber[]
.
这不仅仅是 TS 4.4 的事情,我也尝试过旧版本的 TypeScript,结果没有什么不同。
我认为示例中的方法选择不当(或错误?)。相反,它应该是
type SortedArray<T> = T[] & { readonly __tag: unique symbol };
function sort<T>(array: T[]): SortedArray<T> {
return [...array].sort() as SortedArray<T>;
}
const sorted = sort([3, 7, 1]);
// no error here, but should be:
sorted.push(2);
现在 sorted
仍然是 SortedArray<number>
,但它的元素没有排序。结构类型的问题在于它允许调用在原始类型上定义的所有方法,无论它们是否对新类型有效。 slice
可以,push
、pop
或 reverse
不行。
而且不限于方法,还可以定义函数,如
function shuffle<T extends number[]>(arr: T): T {
arr[0] = 4; // chosen by fair dice roll :-)
return arr;
}
并像
一样使用它const sorted = sort([3, 7, 1]);
const shuffled = shuffle(sorted);
其中 shuffled
将被推断为 SortedArray<number>
。