【问题标题】:Typing functions that operate on similar recursive types对类似递归类型进行操作的键入函数
【发布时间】:2020-09-23 21:33:40
【问题描述】:

在下面的 Typescript 代码中,我有两个结构相似的类型:Nat,它是 ZeroSucc<Nat>,和 Letter,它是 ASucc<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&lt;T&gt;' is not assignable to type 'T'. 'T' could be instantiated with an arbitrary type which could be unrelated to 'Succ&lt;T&gt;'.

我也试过直接重载:

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&lt;T&gt;' 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&lt;T&gt;'.

所以我做了:return new Succ(x) as unknown as T;

工作,但感觉很脏(感觉就像投到 void 并返回)。作为健全性检查,const x: Letter = next(0); 正确地抛出了 Type '0' is not assignable to type 'Letter'. 的类型错误

这里发生了什么?我在写轨道上吗?有没有更优雅的解决方案?

上下文:

我正在开发一个 lambda 演算解释器,来自 函数式编程语言的实现。有几个不同的 AST——核心 lambda 演算、核心 lambda 演算 + let 表达式、核心 lambda 演算 + let + 模式匹配等。目前我设置它的方式是每个构造是一个类,递归的将AST的类型作为参数,所以我们有VariableAbstraction&lt;AST&gt;Application&lt;AST&gt;Let&lt;AST&gt;等,然后每种不同类型的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));
}

这就是我在获取正确类型时遇到的麻烦。

【问题讨论】:

    标签: typescript recursion functional-programming algebraic-data-types


    【解决方案1】:

    这样做的原因:

    function next<T extends Countable>(x: T): T {
      return new Succ(x)
    }
    

    如果x(因此T)的类型为ZeroA,则不起作用,则返回值必须相同。但是Succ&lt;Whatever&gt; 不能分配给ZeroA

    然而,next 似乎永远无法返回ZeroA,只能返回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>>
    

    Playground

    【讨论】:

    • 太棒了!谢谢你的帮助。有什么方法可以表达我正在寻找的传递性?如果T = ZeroNat = Zero ...Nat = Succ&lt;Nat&gt;T = Succ&lt;Zero&gt; 其中= 是类型等价。我正在考虑返回类型可能更复杂但仍应等同于 T 的情况。例如,peano 算术可能会遇到我在编译时无法知道文字类型是什么的情况,但我可以保证等价于T。
    • 我不确定我是否关注,但Zero 永远不会与Succ&lt;Zero&gt; 属于同一类型。一个是数字,一个是实例。您也许可以拥有一个 API,您可以在其中传递一个文字以开始工作,但就像 next 函数一样,它应该始终是 Succ 的一个实例。但如果我完全误解了,请澄清。
    • ZeroSucc&lt;Zero&gt; 不是 Nat 的两个成员吗?因为Nat 是由type Nat = Zero | Succ&lt;Nat&gt; 定义的。我可以编写一个接受Nat 并返回Nat 的函数,它将接受Zero 并返回Succ&lt;Zero&gt;。我还可以编写一个函数,该函数接受Letter 并沿相同的行返回Letter。事实上,我会用完全相同的主体编写这两个函数:return new Succ(x)。现在想象函数add = (x,y) =&gt; x instanceof Succ ? new Succ(add(x.pred, y)) : y。它同样适用于 NatLetter,但我无法编写表达这一点的类型。
    猜你喜欢
    • 2022-01-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-03
    • 2017-12-23
    • 2020-12-15
    相关资源
    最近更新 更多