当前位置: 首页 > 知识库问答 >
问题:

如何对没有显式返回类型的递归函数进行类型检查?

充浩波
2023-03-14

我正在编写一种没有键入函数的语言。这意味着我需要推断函数调用的返回类型,以便进行类型检查。然而,当有人编写递归函数时,类型检查器进入无限递归,试图推断函数体中函数调用的类型。

类型检查器执行如下操作:

  1. 推断函数调用实际参数的类型

然后,步骤4尝试推断函数体内部函数调用的类型,该函数体再次调用相同类型的检查器函数,导致无限递归。

递归函数的一个例子给了我这个问题:

function factorial(n) = n<1 ? 1 : n*factorial(n-1); // Function definition.
...
assert 24 == factorial(4); // Function call expression usage example.

我如何在不进入无限递归循环的情况下解决这个问题?有没有一种方法可以推断递归函数调用的类型,而不必再次进入函数体?或者用一些干净的方法从上下文推断类型?

我知道简单的解决方案可能是向函数添加类型注释,这样问题就很简单了,但在这样做之前,我想知道是否有一种方法可以解决这个问题,而不必求助于它。

我还希望解决方案适用于相互递归。

共有1个答案

卓致远
2023-03-14

根据语言的类型系统以及在需要注释时希望具有的属性,类型推断可能会有很大的不同。但无论你的语言看起来像什么,我认为有一个开创性的案例你真的应该读一读,那就是ML。ML的类型推理有一个很好的最佳点,在一个相对简单的范例中,所有这些都可以结合在一起。不需要类型注释,任何表达式都有一个最通用的类型(这个属性称为类型公理)。

ML的类型系统是Hindley-Milner类型系统,具有参数多态性。表达式的类型可以是特定类型,也可以是“任意”。更准确地说,表达式的类型构造函数要么是特定的类型构造函数,要么是“any”,类型构造函数可以有参数,这些参数本身要么有特定的类型构造函数,要么是“any”。例如,空列表的类型为“任意列表”。可以单独具有“any”类型的两个表达式可能被约束为具有相同的类型,无论它是什么,因此“any”用变量表示。例如,function list\u of_two(x,y)=[x,y](在类似于您的语言的符号中)约束xy具有相同的类型,因为它们插入到相同的列表中,但该类型可以是任何类型,因此该函数的类型是“取相同类型α的任何两个参数,并返回类型列表α的值”。

Hindley-Milner的基本类型推断算法是算法W。在其核心,它通过给每个子表达式一个变量类型来工作:α₁, α₂, α₃, … 然后,编程语言构造对这些变量施加约束。例如,如果一个列表包含两个α类型的元素₁ 和α₂ 列表本身具有类型α₃, 该约束α₁ = α₂ 和α₃ = α列表₁. 将所有这些约束放在一起是一个统一问题。

这些约束基于对程序的纯语法阅读。如果有递归调用,则不需要知道函数的类型:这只是意味着存在一个约束,即函数返回类型的变量与其使用点的类型相同。这只是要添加到约束集的又一个等式。

我忽略了ML的一个重要方面,即表达式的类型可以泛化:表达式可以在不同的地方与不同的类型一起使用。这就是允许多态性的原因。例如,

let empty_list = [] in
(empty_list @ [3]), (empty_list @ ["hello"])

是一个有效的程序,其中empty_list一次用于“整数列表”类型,一次用于“字符串列表”类型。empty_list的类型是“for anyα, list ofα”:这是参数多态性。泛化给算法增加了一些复杂性,但也消除了其他地方的复杂性,因为这是允许原则的。没有它,letempty_list=[]in...将是模棱两可的:empty_list必须有某种类型,但是如果不分析...,就无法知道是什么类型,然后当你分析上面的...时,你需要在整数和字符串之间做出选择。

根据语言的类型系统,ML和算法W可以直接重用,也可以提供一些模糊的启示。但在推理过程中使用变量并逐步约束这些变量的原则非常普遍。

 类似资料:
  • 让我们假设下面的方法(从番石榴的Iterables说): 这个系列: 然后编译以下代码并正确导出泛型 (在Guava中,这将返回< code>objs中所有字符串的iterable。) 但是现在让我们假设以下类: 我不知道如何调用并获取

  • (沙盒) 获取此错误: 类型“number”不可分配给类型“T”“number”可分配给“T”类型的约束,但“T”可以用约束{}的不同子类型实例化。(2322)输入。ts(1,26):预期类型来自此签名的返回类型。 我希望typescript能够自动推断T为数字,然后直接使用它。为什么它在抱怨?写这样的东西的正确方法是什么?谢谢

  • 无法理解如何使用int类型的递归函数查找二叉查找树的高度的解释。 对于任何二元搜索树,给定一个指向树的根节点的指针,其中节点按常规定义如下。。。 我们可以使用以下int类型的递归函数来给出二元搜索树的高度。。。(函数“max”只取两个整数,并返回其中较大的一个) 我的理解是findHeight(根- 我对递归非常陌生,所以我决定只解压缩其中一个递归函数调用,并尝试理解它。我写了findHeight

  • 注意:请通读后再回答。这看起来是一个简单的问题,但我不确定它是否如此简单。另外,我是打字新手,所以对我宽容点 所以这是用例。我有一个模型用户,我编写了一个通用函数来根据数据库中的电子邮件检查用户是否存在。如果存在,则返回用户对象。 现在,如果它是任何其他对象,那么我可以定义类型并继续我的代码,但它是我从DB得到的用户对象,不知道如何解决这个问题。 我通过提到返回类型“any”找到了解决方法,但我的

  • 为了概括这个问题,我借用了Zelenski CS课堂讲义中的材料。而且,这与我的具体问题有关,因为几年前我从另一位讲师那里学习了C语言的这种方法。讲义在这里。我对C的理解很低,因为我偶尔使用它。基本上,我需要编写一个程序的几次,我回到课堂材料,找到类似的东西,然后从那里开始。 在本例(第4页)中,Julie正在字符串函数中使用递归算法查找单词。为了减少递归调用的数量,她添加了一个决策点。 为了增加

  • 在一个使用vanilla JS的项目中,我有一个相当复杂的用例,它使用检查类型,并用JSDoc注释编写类型注释。我有一个返回函数的函数,返回的函数可以递归调用自己,同时也重新分配一些闭包变量。 这是一个愚蠢的例子,它明白了这一点并会抛出同样的错误: 因此,如果我使用对此代码运行类型检查: 我得到以下错误: 首先,这个错误似乎相当错误。我显然有一个显式的返回类型注释,这是使用适当的TypeScrip