Java中的Hindley-Milner算法

我正在开发一个用Java编写的基于数据流的简单系统(想象一下它就像LabView编辑器/运行时)。用户可以在编辑器中将块连接在一起,我需要类型推断来确保数据流图是正确的,但是,大多数类型推理示例都是用数学符号,ML,Scala,Perl等编写的,我不“说话”。

我阅读了有关Hindley-Milner算法的信息,并发现本文档提供了一个我可以实现的很好的示例。它适用于一组类似 T1 = T2 的约束。但是,我的数据流图转换为 T1 >= T2,如约束(或 T2 扩展 T1,或协方差,或 T1 <:T2,正如我在各种文章中看到的那样)。没有 lambdas 只是类型变量(用于泛型函数,如 )和具体类型。T merge(T in1, T in2)

回顾一下 HM 算法:

Type = {TypeVariable, ConcreteType}
TypeRelation = {LeftType, RightType}
Substitution = {OldType, NewType}
TypeRelations = set of TypeRelation
Substitutions = set of Substitution

1) Initialize TypeRelations to the constraints, Initialize Substitutions to empty
2) Take a TypeRelation
3) If LeftType and RightType are both TypeVariables or are concrete 
      types with LeftType <: RightType Then do nothing
4) If only LeftType is a TypeVariable Then
    replace all occurrences of RightType in TypeRelations and Substitutions
    put LeftType, RightType into Substitutions
5) If only RightType is a TypeVariable then
    replace all occurrences of LeftType in TypeRelations and Substitutions
    put RightType, LeftType into Substitutions
6) Else fail

如何更改原始的HM算法以处理这些关系而不是简单的相等关系?Java式的示例或解释将不胜感激。


答案 1

我读了至少20篇文章,发现了一篇(Francois Pottier:存在子类型的类型推断:从理论到实践),我可以使用:

输入:

Type = { TypeVariable, ConcreteType }
TypeRelation = { Left: Type, Right: Type }
TypeRelations = Deque<TypeRelation>

帮助程序函数:

ExtendsOrEquals = #(ConcreteType, ConcreteType) => Boolean
Union = #(ConcreteType, ConcreteType) => ConcreteType | fail
Intersection = #(ConcreteType, ConcreteType) => ConcreteType
SubC = #(Type, Type) => List<TypeRelation>

ExtendsOrEquals可以告诉两个具体类型,如果第一个扩展或等于第二个,例如,(字符串,对象)==true,(对象,字符串)==false。

如果可能的话,联合计算两个具体类型的通用子类型,例如,(Object,Serializable)==Object&Serializable,(Integer,String)==null。

交集计算两种具体类型的最接近的超类型,例如,(列表,集合)==集合,(整数,字符串)==对象。

SubC 是结构分解函数,在这个简单的情况下,它将只返回一个包含其参数的新 TypeRelation 的单例列表。

跟踪结构:

UpperBounds = Map<TypeVariable, Set<Type>>
LowerBounds = Map<TypeVariable, Set<Type>>
Reflexives = List<TypeRelation>

UpperBounds 跟踪类型,这些类型可能是类型变量的超类型,LowerBounds 跟踪可能是类型变量的子类型的类型。自反函数跟踪对类型变量之间的关系,以帮助算法的边界重写。

算法如下:

While TypeRelations is not empty, take a relation rel

  [Case 1] If rel is (left: TypeVariable, right: TypeVariable) and 
           Reflexives does not have an entry with (left, right) {

    found1 = false;
    found2 = false
    for each ab in Reflexives
      // apply a >= b, b >= c then a >= c rule
      if (ab.right == rel.left)
        found1 = true
        add (ab.left, rel.right) to Reflexives
        union and set upper bounds of ab.left 
          with upper bounds of rel.right

      if (ab.left == rel.right)
        found2 = true
        add (rel.left, ab.right) to Reflexives
        intersect and set lower bounds of ab.right 
          with lower bounds of rel.left

    if !found1
        union and set upper bounds of rel.left
          with upper bounds of rel.right
    if !found2
        intersect and set lower bounds of rel.right
          with lower bounds of rel.left

    add TypeRelation(rel.left, rel.right) to Reflexives

    for each lb in LowerBounds of rel.left
      for each ub in UpperBounds of rel.right
        add all SubC(lb, ub) to TypeRelations
  }
  [Case 2] If rel is (left: TypeVariable, right: ConcreteType) and 
      UpperBound of rel.left does not contain rel.right {
    found = false
    for each ab in Reflexives
      if (ab.right == rel.left)
        found = true
        union and set upper bounds of ab.left with rel.right
    if !found 
        union the upper bounds of rel.left with rel.right
    for each lb in LowerBounds of rel.left
      add all SubC(lb, rel.right) to TypeRelations
  }
  [Case 3] If rel is (left: ConcreteType, right: TypeVariable) and
      LowerBound of rel.right does not contain rel.left {
    found = false;
    for each ab in Reflexives
      if (ab.left == rel.right)
         found = true;
         intersect and set lower bounds of ab.right with rel.left
    if !found
       intersect and set lower bounds of rel.right with rel.left
    for each ub in UpperBounds of rel.right
       add each SubC(rel.left, ub) to TypeRelations
  }
  [Case 4] if rel is (left: ConcreteType, Right: ConcreteType) and 
      !ExtendsOrEquals(rel.left, rel.right)
    fail
  }

一个基本的例子:

Merge = (T, T) => T
Sink = U => Void

Sink(Merge("String", 1))

此表达式的关系:

String >= T
Integer >= T
T >= U

1.) rel is (String, T);案例 3 已激活。由于 Reflexives 为空,因此 T 的下限设置为 String。不存在 T 的上限,因此,类型关系保持不变。

2.) rel is (Integer, T);案例 3 再次激活。自反义仍然是空的,T的下限设置为字符串和整数的交集,产生对象,T仍然没有上限,类型关系也没有变化

3.) rel 是 T >= U.案例 1 被激活。因为自反是空的,所以 T 的上限与 U 的上限相结合,U 的上限保持为空。然后将下限 U 设置为下限 ot T,得到对象 >= U。TypeRelation(T, U) 是 Reflexives 的附加项。

4.) 算法终止。从边界开始,对象 >= T 和对象 >= U

在另一个示例中,演示了类型冲突:

Merge = (T, T) => T
Sink = Integer => Void

Sink(Merge("String", 1))

关系:

String >= T
Integer >= T
T >= Integer

步骤 1.)和 2.)同上。

3.) rel 是 T >= U.情况 2 被激活。该案例尝试将 T 的上限(此时为 Object)与整数合并,该整数失败且算法失败。

类型系统的扩展

将泛型类型添加到类型系统在主情况和 SubC 函数中需要扩展。

Type = { TypeVariable, ConcreteType, ParametricType<Type,...>)

一些想法:

  • 如果具体类型和参数类型相遇,则这是一个错误。
  • 如果 TypeVariable 和 ParametricType 相遇,例如 T = C(U1,...,Un),则创建新的 Type 变量和关系,如 T1 >= U1, ... , Tn >= Un 并使用它们。
  • 如果两个参数类型满足 (D<> 和 C<>),请检查 D >= C 以及类型参数的数量是否相同,然后将每对提取为关系。

答案 2

Hindley-Milner算法基本上是一种统一算法,即用于求解具有变量的图方程的图同构的算法。

Hindley-Milner并不直接适用于您的问题,但是Google搜索遇到了一些线索;例如,“多态语言中的实用子类型”,其中说“我们提出了基于名称不等价的Hindley/Milner类型系统的子类型扩展......”。(我没有读过。


...但是,大多数类型推理示例都是用数学符号,ML,Scala,Perl等编写的,我不“说话”。

我认为你将不得不自己克服这个障碍。类型论和类型检查基本上是数学...而且很难。你需要投入坚硬的码来拾取语言。