Подтвердить что ты не робот

Правильная форма letrec в системе типа Hindley-Milner?

У меня возникли проблемы с пониманием определения letrec для системы HM, которое приведено в Википедии, здесь: https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_system#Recursive_definitions

Для меня правило грубо переводится по следующему алгоритму:

  • выводить типы на все в разделе определения letrec
    • присваивать переменные временного типа каждому определенному идентификатору
    • рекурсивно обрабатывать все определения с временными типами
    • в парах, унифицируйте результаты с исходными временными переменными
  • закрыть (с forall) выводимые типы, добавить их в базу (контекст) и вывести типы выражения с ней

У меня возникают проблемы с такой программой:

letrec
 p = (+)     --has type Uint -> Uint -> Uint
 x = (letrec
       test = p 5 5
     in test)
in x

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

  • определение p получает временный тип a
  • определение x также получает некоторый временный тип, но теперь из нашей области
  • in x, определение test получает временный тип t
  • p получает временный тип a из области действия, используя правило HM для переменной
  • (f 5) обрабатывается по правилу HM для приложения, итоговый тип - b (и унификация, которая (a объединяется с Uint -> b)
  • ((p 5) 5) обрабатывается по тому же правилу, что приводит к большему количеству унификаций и типу c, a теперь в результате объединяется с Uint -> Uint -> c
  • теперь тест закрывается, чтобы ввести forall c.c
  • переменная test in test получает экземпляр типа (или forall c.c) со свежими переменными, в соответствии с правилом HM для переменной, в результате чего test :: d (который унифицирован с test::t справа)
  • в результате x эффективно набирает d (или t, в зависимости от настроения унификации)

Проблема: x должен, очевидно, иметь тип Uint, но я не вижу, чтобы эти двое могли объединяться для создания типа. Существует потеря информации, когда тип test закрывается, и экземпляр снова появляется, что я не уверен, как преодолеть или подключиться к подстановкам/унификации.

Любая идея, как алгоритм должен быть исправлен для корректной печати x::Uint? Или это свойство системы HM и просто не будет вводить такой случай (что я сомневаюсь)?

Обратите внимание, что это было бы вполне нормально со стандартным let, но я не хотел обфускать пример с помощью рекурсивных определений, которые невозможно обработать с помощью let.

Заранее спасибо

4b9b3361

Ответ 1

Отвечая на мой собственный вопрос:

Определение в Wiki неверно, хотя оно работает в определенной степени, по крайней мере, для проверки типов.

Самый простой и правильный способ добавить рекурсию к системе HM - использовать предикат fix с определением fix f = f (fix f) и введите forall a. (a->a) -> a. Взаимная рекурсия обрабатывается двойной фиксированной точкой и т.д.

Подход Haskell к проблеме (описанный в https://gist.github.com/chrisdone/0075a16b32bfd4f62b7b#binding-groups) (примерно), чтобы получить неполный тип для всех функций, а затем запустить еще раз, чтобы проверить их друг против друга.