Egzystencjalne vs. powszechnie kwantyfikowane typy w Haskell

Jaka dokładnie jest różnica między nimi? Myślę, że rozumiem jak działają typy egzystencjalne, są jak posiadanie klasy bazowej w OO bez sposobu na obniżenie obsady. Czym różnią się typy uniwersalne?

Author: marli, 2013-01-13

1 answers

Termin" uniwersalny "i" egzystencjalny " pochodzi tutaj od podobnie nazwanych kwantyfikatorów w logice predykatu.

Kwantyfikacja uniwersalna jest zwykle zapisywana jako ∀, które można odczytać jako "dla wszystkich" i oznacza mniej więcej to, jak to brzmi: w logicznym stwierdzeniu przypominającym "∀x...."cokolwiek jest na miejscu"..."jest prawdziwe dla wszystkich możliwych "x", które można wybrać z dowolnego zestawu rzeczy jest obliczana.

Egzystencjalny kwantyfikacja jest zwykle zapisywana jako∃, co można odczytać jako "istnieje" i oznacza, że w logicznym stwierdzeniu przypominającym " ∃x...."cokolwiek jest na miejscu"..."jest prawdą dla jakiegoś nieokreślonego" x " wziętego ze zbioru rzeczy wymiernych.

W Haskell, rzeczy są kwantyfikowane przez są typy (ignorując niektóre rozszerzenia językowe, co najmniej), nasze logiczne stwierdzenia są również typy, i zamiast być" prawdziwe "myślimy o" może być wdrożone".

Tak więc typ uniwersalny jak forall a. a -> a oznacza, że dla każdego możliwego typu " a " możemy zaimplementować funkcję, której typem jest a -> a. I rzeczywiście możemy:

id :: forall a. a -> a
id x = x

Ponieważ a jest powszechnie kwantyfikowane, nic o tym nie wiemy i dlatego nie możemy w żaden sposób zbadać argumentu. Więc id jest jedyną możliwą funkcją tego typu(1).

W Haskell, uniwersalna kwantyfikacja jest "domyślna" --dowolne zmienne typu w sygnaturze są w sposób niejawny powszechnie określany, dlatego typ id jest zwykle zapisywany jako po prostu a -> a. Jest to również znany jako polimorfizm parametryczny, często nazywany po prostu "polimorfizmem" w Haskell, a w niektórych innych językach (np. C#) znany jako "generics".

An egzystencjalnie kwantyfikowany typ jak exists a. a -> a oznacza, że dla jakiegoś konkretnego typu "a", możemy zaimplementować funkcję, której typem jest a -> a. Każda funkcja będzie działać, więc wybieram jeden:

func :: exists a. a -> a
func True = False
func False = True

...co jest oczywiście funkcją " not " na booleanach. Ale haczyk polega na tym, że nie możemy użyć jako takiego, ponieważ wszystko, co wiemy o typie "a", to to, że istnieje. Wszelkie informacje o , który typ może być został odrzucony, co oznacza, że nie możemy zastosować func do żadnych wartości.

To nie jest zbyt przydatne.

Więc co możemy zrobić z func? Wiemy, że jest to funkcja o tym samym typie dla wejścia i wyjścia, więc możemy na przykład skomponować ją samą ze sobą. Zasadniczo jedyne rzeczy, które możesz zrobić z czymś, co ma typ egzystencjalny, to rzeczy, które możesz zrobić w oparciu o nieistniejące części tego typu. Podobnie, biorąc pod uwagę coś typu exists a. [a], możemy znaleźć jego długość, połączyć go z sobą, lub upuścić niektóre elementy, lub cokolwiek innego możemy zrobić z dowolną listą.

[[29]}ten ostatni kawałek sprowadza nas z powrotem do kwantyfikatorów uniwersalnych, i powód, dla którego Haskell(2) nie ma typy egzystencjalne bezpośrednio (mój exists powyżej jest całkowicie fikcyjny, niestety): ponieważ rzeczy z typami egzystencjalnie kwantyfikowanymi mogą być używane tylko z operacjami, które mają powszechnie kwantyfikowane typy, możemy zapisać Typ exists a. a jako forall r. (forall a. a -> r) -> r--innymi słowy, dla wszystkich typów wyników r, biorąc pod uwagę funkcję, która dla wszystkich typów a przyjmuje argument typu a i zwraca wartość typu r, możemy uzyskać wynik typu r.}.

Jeśli nie jest dla Ciebie jasne, dlaczego są one prawie równoważne, zauważ, że ogólny typ nie jest powszechnie kwantyfikowany dla a -- raczej przyjmuje argument, który sam w sobie jest powszechnie kwantyfikowany dla a, który może następnie użyć z dowolnym wybranym typem.


Na marginesie, podczas gdy Haskell nie ma pojęcia podtypu w zwykłym znaczeniu, możemy traktować kwantyfikatory jako wyrażające formę podtypu, z hierarchią przechodzącą od uniwersalnej do konkretnej do egzystencjalnej. Coś typu forall a. a może być zamienione na dowolny inny typ, więc może być postrzegany jako podtyp wszystkiego; z drugiej strony, każdy typ może być konwertowany na typ exists a. a, co czyni go typem nadrzędnym wszystkiego. Oczywiście, pierwszy jest niemożliwy (nie ma wartości typu forall a. a z wyjątkiem błędów), a drugi jest bezużyteczny( nie można nic zrobić z typem exists a. a), ale analogia działa przynajmniej na papierze. :]

Zauważ, że równoważność między typem egzystencjalnym a uniwersalnym argumentem ilościowym działa dla tego samego powód, że wariancja obraca się dla wejść funkcji.


Tak więc, podstawową ideą jest z grubsza to, że powszechnie ilościowo typy opisują rzeczy, które działają tak samo dla każdego typu, podczas gdy typy egzystencjalne opisują rzeczy, które działają z określonym, ale nieznanym typem.


1: cóż, nie do końca-tylko wtedy, gdy ignorujemy funkcje, które powodują błędy, takie jak notId x = undefined, włączając w to funkcje, które nigdy się nie kończą, takie jak loopForever x = loopForever x.

2: GHC. Bez rozszerzenia, Haskell ma tylko Ukryte kwantyfikatory uniwersalne i nie ma realnego sposobu mówienia o typach egzystencjalnych w ogóle.

 83
Author: C. A. McCann,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-13 02:10:52