有一个多步骤的过程,我在我的应用程序中实现了一个状态机,并创建了表示可能的状态转换的类型:
enum ProcessStep {
STEP_1,
STEP_2a,
STEP_2b,
STEP_3
}
type ValidNextStep<Step extends ProcessStep> = {
[ProcessStep.STEP_1]:
| ProcessStep.STEP_2a
| ProcessStep.STEP_2b;
[ProcessStep.STEP_2a]: ProcessStep.STEP_3;
[ProcessStep.STEP_2b]: ProcessStep.STEP_3;
[ProcessStep.STEP_3]: never;
}[Step]
但是我想知道我是否已经在这个图中创建了一个循环,也就是说,ProcessStep.STEP_3
是否可以转换回ProcessStep.STEP_2a
。
如何在类型级别上建立这种不变量呢?看起来很困难,因为默认情况下类型别名不允许循环引用。
发布于 2019-06-19 09:05:28
哇,我喜欢这个问题。我不确定只使用类型系统是否有一种干净或正确的方法来做到这一点。
这里有一种不干净且可能不正确的方法:强制类型系统计算为每个状态启动状态机并运行它一些大量的步骤。如果每个可能的结束状态都是never
,那么就没有循环。否则,要么是图中有圈,要么是图中有一些非常长的非循环路径。
假设您有一个扩展了Record<keyof M, keyof M>
的对象类型M
,这意味着M
的值也是M
的键。这描述了一个状态机(您在ValidNextStep
的定义中有这样一个类型,但是您通过索引它来销毁它……不用担心,我们可以将其重建为{ [K in ProcessStep]: ValidNextStep<K> }
)。对于M
的任何关键K
,您可以计算一个步骤的M[K]
、两个步骤的M[M[K]]
或三个步骤的M[M[M[K]]]
,依此类推。
我们可以非常快速地组合这些操作,以获得数量惊人的步骤:
type TwoSteps<M extends Record<keyof M, keyof M>> = { [K in keyof M]: M[M[K]] };
type FourSteps<M extends Record<keyof M, keyof M>> = TwoSteps<TwoSteps<M>>;
type SixteenSteps<M extends Record<keyof M, keyof M>> = FourSteps<FourSteps<M>>;
type TwoHundredFiftySixSteps<M extends Record<keyof M, keyof M>> = SixteenSteps<
SixteenSteps<M>
>;
如果编译器不对我大喊大叫,我只能做到这一步。
然后,我们可以创建一个见证类型,如果检测到循环(或非常长的路径),该类型会导致编译器错误:
type NoCycles<
N extends never = TwoHundredFiftySixSteps<
{ [K in ProcessStep]: ValidNextStep<K> }
>[ProcessStep]
> = true;
对于您对ValidNextStep
的原始定义来说,这很好,但如果我们将其更改为以下内容:
type ValidNextStep<Step extends ProcessStep> = {
[ProcessStep.STEP_1]: ProcessStep.STEP_2a | ProcessStep.STEP_2b;
[ProcessStep.STEP_2a]: ProcessStep.STEP_3;
[ProcessStep.STEP_2b]: ProcessStep.STEP_3;
[ProcessStep.STEP_3]: ProcessStep.STEP_2a; // oops
}[Step];
则NoCycles
定义会产生以下错误:
// Type 'ProcessStep.STEP_2a | ProcessStep.STEP_3' does not satisfy the constraint 'never'.
这表明,在运行了256个步骤之后,仍然可以处于STEP_2a
或STEP_3
中,这表示一个周期。
这是个好主意吗?可能不会..。不能保证它是正确的,而且它可能会给编译器带来比保证更多的压力。但我不知道我想找更好的工作有多难。使用通用语言,您会尝试找到some efficient algorithm来检测循环,但TypeScript中的类型系统不太可能具有足够的表达能力来实现它。
因此,使用它的风险自负,祝你好运!
https://stackoverflow.com/questions/56658241
复制相似问题