Алгоритм Хиндли-Милнера в Java

Я работаю над простой системой на основе потока данных (представьте, что это как редактор LabView/среда выполнения), написанная на Java. Пользователь может соединять блоки вместе в редакторе, и мне нужен вывод типа, чтобы убедиться, что график потока данных правильный, однако большинство примеров вывода типа написаны в математических обозначениях, ML, Scala, Perl и т. д. на котором я не "говорю".

Я прочитал об алгоритме Хиндли-Милнера и нашел этой документ с хорошим примером, который я мог бы реализовать. Он работает на множестве ограничений T1 = T2. Однако мои графики потока данных преобразуются в T1 >= T2 как ограничения (или T2 расширяет T1, или ковариацию, или T1 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-ish или я был бы весьма признателен за объяснение.

2 ответов


Я прочитал не менее 20 статей и нашел одну (Франсуа Потье: вывод типа при наличии подтипа: от теории к практике), которую я мог бы использовать:

вход:

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 может рассказать о двух конкретных типах, если первый расширяет или равен второму, например, (String, Object) = = true, (Object, String) = = false.

Union вычисляет общий подтип двух конкретных типов, если это возможно, например, (Object, Serializable) = = Object&Serializable, (Integer, String) = = null.

пересечение вычисляет ближайший супертип двух конкретных типов, например, (List, Set) = = Collection, (Integer, String) = = Object.

SubC-это функция структурного разложения, которая в этом простом случае просто вернет одноэлементный список, содержащий новую Типереляцию его параметров.

отслеживание структуры:

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

UpperBounds отслеживает типы, которые могут быть супертипы типа переменной, LowerBounds отслеживает типы, которые могут быть подтипы типа переменной. Reflexives отслеживает отношения между переменными типа пар, чтобы помочь в переписывании алгоритма.

алгоритм выглядит следующим образом:

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 установлены в строку. Нет верхних границ для T, поэтому Типереляции остаются неизменными.

2.) rel (целое число, T); Случай 3 активируется снова. Рефлексы по-прежнему пусты, нижняя граница T установлена на пересечение строки И целого числа, что дает объект, по-прежнему нет верхних границ для T и никаких изменений в Типереляциях

3.) rel - T >= U. корпус 1 активирован. Поскольку рефлексы пусты, верхние границы T в сочетании с верхней границей U, которая остается пустой. Затем нижние границы U устанавливаются на нижние границы ot T, давая Object >= U. Типереляция (T, U) добавляется к Рефлексивам.

4.) алгоритм завершается. Из границ Object >= T и Object >= 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 (которая является объектом в этой точке) с целым числом, которое терпит неудачу, и алгоритм терпит неудачу.

расширения для системы типа

добавление универсальных типов в систему типов требует расширения в основных случаях и в функции SubC.

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

некоторые идеи:

  • если ConcreteType и ParametricType встречаются, то это ошибка.
  • если TypeVariable и ParametricType встречаются, например, T = C (U1,..., Un) затем создайте переменные и отношения нового типа как T1 >= U1,... , Tn >= Un и работать с ними.
  • если встречаются два параметрических типа (D и C), проверьте, совпадают ли D >= C и количество аргументов типа, затем извлеките каждую пару как отношения.

алгоритм Хиндли-Милнера является принципиально алгоритмом унификации, т. е. алгоритмом решения изоморфизмов графа для графовых уравнений с переменными.

Hindley-Milner не относится непосредственно к вашей проблеме, но поиск Google наткнулся на некоторые зацепки; например "прагматический подтип в полиморфных языках", который сказал " мы представляем расширение подтипа для системы типа Хиндли / Милнера, которая основана на неравенстве имен ...". (Я не читал.)


... однако большинство примеров вывода типа записываются в математических обозначениях, ML, Scala, Perl и т. д. на котором я не "говорю".

Я думаю, тебе придется преодолеть это препятствие самостоятельно. Теория типов и проверка типов фундаментально математичны ... и сложно. Вам нужно поставить в жесткие ярды, чтобы подобрать язык.