首页 > 解决方案 > TypeScript 中具有类型保护和交集类型的新类型模式

问题描述

我正在阅读这篇关于 TS 中的新型模式的文章。

作者展示了使用交集类型和假盒子类型之间的区别。后者是您主要在所有实现中发现的(例如参见 fp-ts 和 lib newtype-ts),因为缺少在“交叉类型”部分末尾描述的不变量:

更重要的是,由于 TypeScript 是结构化类型的,我们可以将 newtype 的值传递到任何需要基本类型的地方。一般来说,这可能非常好,因为我们可以在原始的所有实用程序函数中使用该类型,但如果我们想保持该值的某些不变量,也可能是非常不希望的:

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 中实现了这个例子,我可能会遗漏一些东西,因为 of 的类型notSortedAnymore显然不是 aSortedArray而是 a number[]

对我来说,这似乎不是问题,因为类型系统不会被该concat方法的使用所误导。我有一个 SortedArray,我连接数字 2,无法推断它仍然是一个 SortedArray,所以它给了我一个数字数组。

鉴于在 TS 中,对这种模式使用交集类型可以实现更简单(甚至可能更清晰)(= 您可以避免使用该lift函数来应用简单的函数,例如concat),为什么 newtype 模式通常是使用假盒子类型而不是交叉类型实现?我在这里想念什么

非常感谢

最好的

标签: typescriptfunctional-programmingnewtype

解决方案


我可能会遗漏一些东西,因为类型notSortedAnymore显然不是 aSortedArray而是 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仍然是 aSortedArray<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);

whereshuffled将被推断为 a SortedArray<number>


推荐阅读