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

时间:2019-08-08 20:37:15

标签: function recursion compiler-construction typechecking function-call

我正在写一种不键入函数的语言。这意味着我需要推断函数调用的返回类型以进行类型检查。但是,当有人编写递归函数时,类型检查器将进行无限递归,以尝试推断函数体内的函数调用类型。

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

  1. 推断函数调用实际参数的类型。
  2. 创建实际参数类型到形式参数的映射。
  3. 使用该映射在函数体内使用的参数上注释类型。
  4. 推断并返回函数体的返回类型。

第4步试图在函数体内推断函数调用的类型,该函数调用再次调用同一类型检查器函数,从而导致无限递归。

给我这个问题的递归函数示例:

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

如何在不进入无限递归循环的情况下解决此问题?有没有一种方法可以推断递归函数调用的类型而不必再次进入主体?还是一些从上下文推断类型的简洁方法?

我知道简单的解决方案可能是在函数中添加类型注释,这样问题就变得微不足道了,但是在此之前,我想知道是否有一种方法可以解决而不求助于此。

我也希望解决方案可以相互递归使用。

1 个答案:

答案 0 :(得分:2)

根据语言的类型系统以及在需要注释时要具有的属性,类型推断可能会有很大不同。但是无论您使用哪种语言,我都认为您确实应该阅读一个开创性的案例,即ML。 ML的类型推断在一个相对简单的范例中可以很好地融合在一起,是一个不错的选择。不需要类型批注,并且任何表达式都具有单个最通用的类​​型(此属性称为“ 主要性”)。

ML的类型系统是Hindley-Milner type system,它具有parametric polymorphism。表达式的类型可以是特定类型,也可以是“任意”。更准确地说,表达式的类型 constructor 是特定的类型构造函数或“ any”,并且类型构造函数可以具有本身具有特定类型构造函数或“ any”的参数。例如,空列表的类型为“任何列表”。可以将两个可以单独具有“ any”类型的表达式约束为具有相同类型,无论它是什么类型,因此“ any”可以用变量表示。例如,function list_of_two(x, y) = [x, y](用您的语言表示)限制xy具有相同的类型,因为它们被插入相同的列表中,但是该类型可以是任何类型,因此此函数的类型为“取相同类型α的任意两个参数,并返回一个类型列表的值”。

Hindley-Milner的基本类型推断算法为algorithm W。它的核心是为每个子表达式赋予一个变量类型:α₁,α2,α₃,……编程语言构造然后对这些变量施加约束。例如,如果一个列表包含两个类型为α1和α2的元素,并且列表本身具有类型为α1,则该约束α1 =α2并且α1 =α1的列表。将所有这些约束放在一起是一个unification问题。

约束是基于对程序的纯粹语法读取。如果有递归调用,则不需要知道函数的类型:这仅意味着存在一个约束,即函数返回类型的变量与使用时的类型相同。那只是添加到约束集合中的另一个方程式。

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

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

是有效的程序,其中empty_list与“整数列表”类型一起使用,而与“字符串列表”类型一起使用一次。 empty_list的类型是“对于任何α,α列表”:这是参数多态性。泛化为算法增加了一些复杂性,但同时也消除了其他地方的复杂性,因为这就是公因性的原因。没有它,let empty_list = [] in …就会模棱两可:empty_list必须具有某种类型,但是如果不分析,然后再分析{{1 }}以上,您需要在整数和字符串之间进行选择。

根据语言类型的不同,机器学习和算法W可能会直接重用,或者可能会带来一些模糊的启发。但是在推理过程中使用变量并逐步约束这些变量的原理非常普遍。