问题描述
在下面的Typescript代码中,我有两种结构上相似的类型:Nat
(可以是Zero
或Succ<Nat>
,以及Letter
,可以是A
或Succ<Letter>
,其中Succ
是简单的容器类型。
type Zero = 0;
type A = 'A';
class Succ<T> {
constructor(public pred: T){}
}
type Nat= Zero | Succ<Nat>
type Letter = A | Succ<Letter>
function next(x: ?): ?{
return new Succ(x);
}
我的问题是,如何键入next
函数,该函数基本上只是包装在函数调用中的Succ
的构造函数?本质上是声明,如果您通过Nat
,则返回Nat
,如果您通过Letter
,则返回Letter
。
我尝试过:
type Countable = Nat | Letter
function next<T extends Countable>(x: T): T { ...
我得到Type 'Succ<T>' is not assignable to type 'T'. 'T' Could be instantiated with an arbitrary type which Could be unrelated to 'Succ<T>'.
我也尝试直接将其重载:
function next(x: Nat): Nat;
function next(x: Letter): Letter {
return new Succ(x);
}
我尝试直接投射它:
function next<T extends Countable>(x: T): T {
return new Succ(x) as T;
}
返回错误Conversion of type 'Succ<T>' to type 'T' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional,convert the expression to 'unkNown' first. 'T' Could be instantiated with an arbitrary type which Could be unrelated to 'Succ<T>'.
所以我做到了:return new Succ(x) as unkNown as T;
该可行,但是感觉很脏(感觉像是铸造到空隙和背面)。作为健全性检查,const x: Letter = next(0);
会正确抛出类型错误,Type '0' is not assignable to type 'Letter'.
这是怎么回事?我在写轨道上吗?有更优雅的解决方案吗?
上下文:
我正在使用功能编程语言的实现中的lambda演算解释器。有几个不同的AST-核心lambda演算,核心lambda演算+ let
表达式,核心lambda演算+ let
+模式匹配等。目前,我的设置方式就是每个构造都是一个类,而递归构造将AST的类型作为参数,因此我们有Variable
,Abstraction<AST>
,Application<AST>
,Let<AST>
等,然后将每种不同类型的AST表示为联合,所以
type OrdinaryLambdaCalculus =
| Variable
| Abstraction<OrdinaryLambdaCalculus>
| Application<OrdinaryLambdaCalculus>;
type AugmentedLambdaCalculus =
| Variable
| Abstraction<AugmentedLambdaCalculus>
| Application<AugmentedLambdaCalculus>
| Let<AugmentedLambdaCalculus>
到目前为止,它的运行情况还不错,但是我希望能够编写一个函数,该函数接受一个或多个AST表达式,并通过添加结构来构造相同类型的新AST,例如:
function etaAbstraction<T extends AST>(ast: T): T {
let newVar = findFree(ast);
return new Abstraction(newVar,new Application(ast,newVar));
}
这就是我遇到的麻烦,无法正确输入类型。
解决方法
原因如下:
function next<T extends Countable>(x: T): T {
return new Succ(x)
}
不起作用的是,如果x
(因此T
)的类型为Zero
或A
,则返回值必须相同。但是Succ<Whatever>
无法分配给Zero
或A
。
但是,似乎next
永远不会返回Zero
或A
,只能返回Succ
的一个实例。因此,如果您这样输入,它将按您期望的那样工作。
type Countable = Nat | Letter
function next<T extends Countable>(x: T): Succ<T> {
return new Succ(x);
}
const a = next(0) // Succ<0>
const b = next(a) // Succ<Succ<0>>