Z tego odniesienia: Ścisła pozytywność
Surowy warunek dodatni wyklucza deklaracje takie jak
data Bad : Set where
bad : (Bad → Bad) → Bad
A B C
-- A is in a negative position, B and C are OK
Dlaczego A jest ujemne? Również dlaczego B jest dozwolone? Rozumiem, dlaczego C jest dozwolone.
type-theory
inductive-datatypes
Pushpa
źródło
źródło
A
i ostatecznie eksplodować stosu (w językach opartych na stosie).Odpowiedzi:
Najpierw wyjaśnienie terminologiczne: pozycje negatywne i pozytywne pochodzą z logiki. Są o asymetrii w zdaniotwórczych logiczne: w zachowuje się inaczej B . Podobnie dzieje się w teorii kategorii, gdzie mówimy kontrawariantny i kowariantna zamiast negatywnych i pozytywnych, odpowiednio. W fizyce mówią o wielkościach, które zachowują się „kowariantnie” i „również przeciwnie”. Jest to więc bardzo ogólne zjawisko. Programista może myśleć o nich jako o „danych wejściowych” i „danych wyjściowych”.A ⇒ B ZA b
Teraz na indukcyjne typy danych.
W algebrze zwyczajowo operacja pobiera skończoną liczbę argumentów, aw większości przypadków przyjmuje zero (stała), jeden (unary) lub dwa (binarne) argumenty. Uogólnienie jest wygodne dla konstruktorów typów danych. Załóżmy, że
c
jest konstruktorem typu danychT
:c
jest stałą, możemy myśleć o niej jako o funkcjiunit -> T
lub równoważnie(empty -> T) -> T
,c
jest jednoargumentowy, możemy myśleć o tym jako o funkcjiT -> T
lub równoważnie(unit -> T) -> T
,c
jest binarny, możemy myśleć o nim jako o funkcjiT -> T -> T
lub równoważnieT * T -> T
lub równoważnie(bool -> T) -> T
,c
który przyjmuje siedem argumentów, moglibyśmy postrzegać go jako funkcję, w(seven -> T) -> T
którejseven
jest jakiś wcześniej zdefiniowany typ z siedmioma elementami.c
który przyjmuje niezliczoną liczbę argumentów, co byłoby funkcją(nat -> T) -> T
.Te przykłady pokazują, że ogólna forma konstruktora powinna być
gdzie my nazywamy
A
ten arity sięc
i myślimyc
jako konstruktor, że trwaA
to wiele argumentów typuT
produkować elementT
.Oto coś bardzo ważnego: arie muszą zostać zdefiniowane przed zdefiniowaniem
T
, w przeciwnym razie nie będziemy w stanie powiedzieć, co powinni robić konstruktorzy. Jeśli ktoś próbuje mieć konstruktoranastępnie pytanie „ile argumentów
broken
potrzeba?” nie ma dobrej odpowiedzi. Możesz spróbować odpowiedzieć na to „potrzebaT
wielu argumentów”, ale to nie zadziała, ponieważT
nie zostało jeszcze zdefiniowane. Możemy próbować wydostać się z trybuny, stosując fantazyjną teorię punktu stałego, aby znaleźć typT
i funkcję iniekcyjną(T -> T) -> T
, i to się udałoby, ale po drodze złamalibyśmy zasadę indukcjiT
. Tak więc to po prostu zły pomysł, aby spróbować czegoś takiego.c
B
Rzeczywiście, wiele konstruktorów można przepisać w ten sposób, ale nie wszyscy, potrzebujemy jeszcze jednego kroku, a mianowicie powinniśmy pozwolić
A
na poleganie naB
:Jest to ostateczna forma konstruktora typu indukcyjnego. Dokładnie takie są też typy W. Forma jest tak ogólna, że zawsze potrzebujemy tylko jednego konstruktora
c
! Rzeczywiście, jeśli mamy dwa z nichwtedy możemy połączyć je w jedno
gdzie
Nawiasem mówiąc, jeśli curry ogólną formę, zobaczymy, że jest ona równoważna
co jest bliższe temu, co ludzie zapisują w asystentach dowodowych. Asystenci dowodu pozwalają nam zapisać konstruktory w wygodny sposób, ale są one równoważne z powyższą ogólną formą (ćwiczenie!).
źródło
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.
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ść:
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):
Oto analogiczny przykład w Ocaml, który pokazuje, jak zaimplementować zachowanie rekurencyjne bez (!) Bezpośredniego użycia rekurencji:
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):Następnie
loop 0
sprawdzanie typuloop 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.
źródło