Pierwsze wystąpienie Bad
nazywa się „negatywne”, ponieważ reprezentuje argument funkcji, tj. Znajduje się po lewej stronie strzałki funkcji (patrz typy rekurencyjne za darmo przez Philipa Wadlera). Myślę, że pochodzenie terminu „pozycja negatywna” wynika z pojęcia kontrowariancji („contra” oznacza przeciwieństwo).
Niedopuszczalne jest definiowanie typu w pozycji ujemnej, ponieważ można w nim pisać programy nie kończące się, tzn. Silna normalizacja zawodzi w jego obecności (więcej na ten temat poniżej). Nawiasem mówiąc, jest to przyczyną nazwy reguły „ścisła pozytywność”: nie dopuszczamy pozycji negatywnych.
Dopuszczamy drugie wystąpienie, Bad
ponieważ nie powoduje to braku zakończenia i chcemy użyć typu zdefiniowanego ( Bad
) w pewnym momencie w rekurencyjnym typie danych ( przed ostatnią strzałką jego konstruktora).
Ważne jest, aby zrozumieć, że poniższa definicja nie narusza ścisłej zasady pozytywności.
data Good : Set where
good : Good → Good → Good
Reguła dotyczy tylko argumentów konstruktora (które są oba Good
w tym przypadku), a nie samego konstruktora (patrz także „ Certyfikowane programowanie z typami zależnymi ” Adama Chlipali ).
Kolejny przykład naruszający ścisłą pozytywność:
data Strange : Set where
strange : ((Bool → Strange) → (ℕ → Strange)) → Strange
^^ ^
this Strange is ...this arrow
to the left of...
Możesz także sprawdzić tę odpowiedź na temat negatywnych pozycji.
Więcej informacji o rozwiązaniu umowy ... Strona, do której się odwołujesz, zawiera kilka wyjaśnień (wraz z przykładem w Haskell):
Deklaracje, które nie są ściśle dodatnie, są odrzucane, ponieważ można na ich podstawie napisać funkcję nie kończącą się. Aby zobaczyć, jak można napisać definicję zapętlenia przy użyciu typu danych Bad z góry, zobacz BadInHaskell .
Oto analogiczny przykład w Ocaml, który pokazuje, jak zaimplementować zachowanie rekurencyjne bez (!) Bezpośredniego użycia rekurencji:
type boxed_fun =
| Box of (boxed_fun -> boxed_fun)
(* (!) in Ocaml the 'let' construct does not permit recursion;
one have to use the 'let rec' construct to bring
the name of the function under definition into scope
*)
let nonTerminating (bf:boxed_fun) : boxed_fun =
match bf with
Box f -> f bf
let loop = nonTerminating (Box nonTerminating)
W nonTerminating
funkcji „rozpakowuje” funkcja z jego argumentów i jabłka do oryginalnego argumentu. Ważne jest tutaj to, że większość systemów typów nie zezwala na przekazywanie funkcji do siebie, więc termin typu „ f f
nie będzie sprawdzania typów”, ponieważ nie ma typu, f
który mógłby zaspokoić funkcję sprawdzania typu. Jednym z powodów, dla których wprowadzono systemy typu, jest wyłączenie samowystarczalności (patrz tutaj ).
Zawijanie typów danych, takich jak ten, który wprowadziliśmy powyżej, można wykorzystać do obejścia tej blokady na drodze do niespójności.
Chcę dodać, że obliczenia nie kończące się wprowadzają niespójności do systemów logicznych. W przypadku Agdy i Coq False
indukcyjny typ danych nie ma żadnych konstruktorów, więc nigdy nie można zbudować terminu próbnego typu False. Ale jeśli dozwolone byłyby obliczenia nie kończące się, można to zrobić na przykład w ten sposób (w Coq):
Fixpoint loop (n : nat) : False = loop n
Następnie loop 0
sprawdzanie typu loop 0 : False
, więc w korespondencji Curry-Howarda oznaczałoby to, że okazaliśmy się fałszywą propozycją.
Wynik : ścisła reguła pozytywności dla definicji indukcyjnych zapobiega niekończącym się obliczeniom, które są katastrofalne dla logiki.
A
i ostatecznie eksplodować stosu (w językach opartych na stosie).