Trudno jest coś dodać do wyjaśnień Andreja lub Neela, ale dam temu szansę. Spróbuję zająć się syntaktycznym punktem widzenia, zamiast próbować odkryć leżącą u podstaw semantykę, ponieważ wyjaśnienie jest bardziej elementarne i udzielę bardziej prostej odpowiedzi na twoje pytanie.
Zamierzam pracować w prostym typie -calculus zamiast w bardziej złożonym systemie leżącym u podstaw Haskell. Uważam w szczególności, że obecność zmiennych typu może do pewnego stopnia dezorientować.λ
Najważniejsze odniesienie jest następujące:
Mendler, N. (1991). Typy indukcyjne i ograniczenia typów w rachunku lambda drugiego rzędu. Obawiam się, że nie znalazłem referencji online. Stwierdzenia i dowody można jednak znaleźć w rozprawie doktorskiej Naxa (wysoce zalecana lektura!).
Mendler wyjaśnia, że pozytywność jest koniecznym i wystarczającym warunkiem zakończenia w przypadku nierekurencyjnych definicji przypadków (i strukturalnie malejących definicji rekurencyjnych). Stwierdza to za pomocą równania równania . Podaję prosty przykład, który jest uproszczeniem typu .Bad
Bad=Bad→A
Gdzie jest dowolnym typem. Mamy wtedyA
λx:Bad.x x:Bad→A
a więc
(λx:Bad.x x) (λx:Bad.x x):A
Mendler pokazuje, że można tego dokonać dla dowolnego typu
gdzie jest typem z co najmniej jednym ujemnym wystąpieniem (mogą również występować pozytywne) . Podaje wyraźny termin, który nie kończy się dla danego (strony 39-40 jego pracy).
Bad=F(Bad)
F(X)XF(X)
Oczywiście nie pracujesz z typami równymi zdefiniowanymi, ale z konstruktorami , tzn. Masz
data Bad = Pack (Bad -> A)
zamiast ścisłej równości. Jednak możesz to zdefiniować
unpack :: Bad -> (Bad -> A)
unpack (Pack f) = f
co jest wystarczające do utrzymania tego wyniku:
(\x:Bad -> unpack x x) (Pack (\x:Bad -> unpack x x))
Termin ten jest nadal dobrze wpisane typu .A
W drugim przykładzie sprawy są nieco trudniejsze, ponieważ masz coś podobnego do
Bad=Bad′→A
gdzie jest powiązany , ale nie równy, z (w twoim przypadku są one równe i odpowiednio). Przyznaję, że nie mogłem zbudować prostego izomorfizmu między nimi. Ten sam problem występuje, jeśli go wymieniszB d B d B d ( N O T )Bad′BadBad aBad (Not a)
type Not a = a -> False
z
data Not a = Not a
Łatwo byłoby to rozwiązać, gdyby Haskell zezwolił na takie definicje typów:
type Acc = Not Acc
W takim przypadku można zbudować kombinator pętli w dokładnie taki sam sposób jak poprzednio. Podejrzewam, że możesz nosić podobną (ale bardziej złożoną) konstrukcję
data Acc = D (Not Acc)
Problem polega na tym, że zbudować izomorfizm
Bad Acc <-> Bad (Not Acc)
masz do czynienia z wariancją mieszaną.