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 可以,pushpopreverse 不行。

而且不限于方法,还可以定义函数,如

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>