Перевірка типів та рекурсивні типи (написання комбінатора Y в Haskell / Ocaml)


21

Коли пояснюється комбінатор Y в контексті Haskell, зазвичай зазначається, що пряма реалізація не перевірить тип в Haskell через його рекурсивного типу.

Наприклад, з Rosettacode :

The obvious definition of the Y combinator in Haskell canot be used
because it contains an infinite recursive type (a = a -> b). Defining
a data type (Mu) allows this recursion to be broken.

newtype Mu a = Roll { unroll :: Mu a -> a }

fix :: (a -> a) -> a
fix = \f -> (\x -> f (unroll x x)) $ Roll (\x -> f (unroll x x))

І справді, "очевидне" визначення не набирає перевірки:

λ> let fix f g = (\x -> \a -> f (x x) a) (\x -> \a -> f (x x) a) g

<interactive>:10:33:
    Occurs check: cannot construct the infinite type:
      t2 = t2 -> t0 -> t1
    Expected type: t2 -> t0 -> t1
      Actual type: (t2 -> t0 -> t1) -> t0 -> t1
    In the first argument of `x', namely `x'
    In the first argument of `f', namely `(x x)'
    In the expression: f (x x) a

<interactive>:10:57:
    Occurs check: cannot construct the infinite type:
      t2 = t2 -> t0 -> t1
    In the first argument of `x', namely `x'
    In the first argument of `f', namely `(x x)'
    In the expression: f (x x) a
(0.01 secs, 1033328 bytes)

Таке ж обмеження існує і в Ocaml:

utop # let fix f g = (fun x a -> f (x x) a) (fun x a -> f (x x) a) g;;
Error: This expression has type 'a -> 'b but an expression was expected of type 'a                                    
       The type variable 'a occurs inside 'a -> 'b

Однак у Ocaml можна дозволити рекурсивні типи, передаючи -rectypesкомутатор:

   -rectypes
          Allow  arbitrary  recursive  types  during type-checking.  By default, only recursive
          types where the recursion goes through an object type are supported.

Використовуючи -rectypesвсе, працює:

utop # let fix f g = (fun x a -> f (x x) a) (fun x a -> f (x x) a) g;;
val fix : (('a -> 'b) -> 'a -> 'b) -> 'a -> 'b = <fun>
utop # let fact_improver partial n = if n = 0 then 1 else n*partial (n-1);;
val fact_improver : (int -> int) -> int -> int = <fun>
utop # (fix fact_improver) 5;;
- : int = 120

Будучи цікавим щодо типів і типів висновку, це викликає деякі питання, на які я досі не можу відповісти.

  • По-перше, як перевіряє тип придумує тип t2 = t2 -> t0 -> t1? Придумавши цей тип, я думаю, що проблема полягає в тому, що тип ( t2) відноситься до себе з правого боку?
  • По-друге, і, мабуть, найцікавіше, у чому причина систем типу Haskell / Ocaml заборонити це? Я думаю, що є вагома причина, оскільки Ocaml також не дозволить цього за замовчуванням, навіть якщо він може мати справу з рекурсивними типами, якщо дано-rectypes комутатор.

Якщо це справді великі теми, я би вдячний вказівникам на відповідну літературу.

Відповіді:


16

По-перше, помилка GHC,

GHC намагається уніфікувати кілька обмежень x, по-перше, ми використовуємо це як функцію

x :: a -> b

Далі ми використовуємо це як значення для цієї функції

x :: a

І, нарешті, ми об'єднаємо це з оригінальним виразом аргументу так

x :: (a -> b) -> c -> d

Тепер x xстає спробою об'єднати t2 -> t1 -> t0, однак, ми не можемо це об'єднати, оскільки це вимагатиме об'єднання t2, першого аргументу x, з x. Звідси наше повідомлення про помилку.

Далі, чому б не загальні рекурсивні типи. Ну, першим моментом варто відзначити різницю між типами equi та iso рекурсивними,

  • еквірекурсивні - це те, що ви очікуєте, mu X . Typeє рівнозначним розширенню чи згортанню його довільно.
  • ISO-рекурсивні типи забезпечують пару операторів, foldі unfoldякий складати і розкладати рекурсивні визначення типів.

Зараз одинично-рекурсивні типи звучать ідеально, але їх важко отримати в складних системах. Це фактично може зробити перевірку типу нерозбірливою. Я не знайомий з усіма деталями системи типів OCaml, але цілком еквівалентні типи в Haskell можуть призвести до того, що перевіряючий тип циклу довільно намагається уніфікувати типи, за замовчуванням Haskell гарантує, що перевірка типу припиняється. Більше того, в Haskell синоніми типу є німими, найбільш корисні рекурсивні типи визначатимуться як type T = T -> (), однак в Haskell вбудовані майже відразу, але ви не можете вбудувати рекурсивний тип, це нескінченно! Тому рекурсивні типи в Haskell вимагатимуть величезного перегляду того, як обробляються синоніми, ймовірно, не варто докладати зусиль навіть як розширення мови.

Ізорекурсивні типи - це неприємно використовувати, вам більш-менш потрібно чітко сказати перевіряючому типу, як складати та розгортати типи, що робить ваші програми складнішими для читання та запису.

Однак це дуже схоже на те, що ви робите зі своїм Muтипом. Rollскладається і unrollрозгортається. Тому насправді у нас є рекурсивні типи. Однак екві-рекурсивні типи є надто складними, щоб такі системи, як OCaml та Haskell, змушували вас пропускати рецидиви через типові виправлення на рівні типу.

Тепер, якщо це вас цікавить, я рекомендую типи та мови програмування. Моя копія сидить відкрито на колінах, коли я пишу це, щоб переконатися, що я отримав потрібну термінологію :)


Зокрема, глава 21 пропонує добру інтуїцію щодо індукції, коіндукції та рекурсивних типів
Даніель Гратцер,

Дякую! Це дійсно захоплююче. Зараз я читаю TAPL, і я радий почути, що це буде висвітлено пізніше в книзі.
бета

@beta Yep, TAPL та його старший брат, розширені теми у видах та мовах програмування - чудові ресурси.
Даніель Гратцер

2

В OCaml вам потрібно передати -rectypesяк параметр компілятору (або ввести #rectypes;;в верхній рівень). Грубо кажучи, це вимкне "перевірку трапляється" під час об'єднання. Ситуація The type variable 'a occurs inside 'a -> 'bбільше не буде проблемою. Система типів все ще буде "правильною" (звук тощо), нескінченні дерева, що виникають у вигляді типів, іноді називають "раціональними деревами". Система типу слабшає, тобто неможливо виявити деякі помилки програміста.

Дивіться мою лекцію з обчислення лямбда (починаючи з слайда 27) для отримання додаткової інформації про операторів фіксованих точок з прикладами в OCaml.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.