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

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

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

Ответ 1

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

Input:

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.

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

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

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

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

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 пуст, LowerBounds of T устанавливается в String. Нет UpperBounds для T, поэтому TypeRelations остается неизменным.

2.) rel is (Integer, T); Случай 3 снова активирован. Reflexives все еще пуст, нижняя граница T установлена ​​на пересечение String и Integer, давая Object, Тем не менее верхние границы для T и никаких изменений в TypeRelations

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

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 (которая является объектом в этой точке) с Integer, которая терпит неудачу и алгоритм терпит неудачу.

Расширения системы Type

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

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

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

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

Ответ 2

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

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


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

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