Zaczynam rozumieć, w jaki sposób forall
słowo kluczowe jest używane w tak zwanych „typach egzystencjalnych”, takich jak:
data ShowBox = forall s. Show s => SB s
Jest to jednak tylko podzbiór tego, w jaki sposób forall
jest używany i po prostu nie mogę skupić się na jego użyciu w takich rzeczach:
runST :: forall a. (forall s. ST s a) -> a
Lub wyjaśnienie, dlaczego są one różne:
foo :: (forall a. a -> a) -> (Char, Bool)
bar :: forall a. ((a -> a) -> (Char, Bool))
Lub cała RankNTypes
sprawa ...
Wolę raczej czysty, pozbawiony żargonu angielski niż te, które są normalne w środowisku akademickim. Większość wyjaśnień na ten temat (które mogę znaleźć w wyszukiwarkach) ma następujące problemy:
- Są niekompletne. Wyjaśniają, jedną część używania tego słowa kluczowego (na przykład „typów egzystencjalnych”), co sprawia, że czuję się szczęśliwy, dopóki nie przeczytałem kod, który używa go w zupełnie inny sposób (jak
runST
,foo
ibar
powyżej). - Są gęsto wypełnione założeniami, że czytałem najnowsze w jakiejkolwiek gałęzi dyskretnej matematyki, teorii kategorii lub algebry abstrakcyjnej, która jest popularna w tym tygodniu. (Jeśli nie czytam słowa „konsultować papier co do szczegółów realizacji” ponownie, będzie to zbyt szybko).
- Są napisane w sposób, który często zamienia nawet proste pojęcia w kręto pokręconą i złamaną gramatykę i semantykę.
Więc...
Przejdź do właściwego pytania. Czy ktoś może całkowicie wyjaśnić forall
słowo kluczowe w jasnym, prostym języku angielskim (lub, jeśli istnieje, wskazać tak jasne wyjaśnienie, którego mi brakowało), które nie zakłada, że jestem matematykiem pogrążonym w żargonie?
Edytowano, aby dodać:
Poniżej były dwie wyróżniające się odpowiedzi spośród tych o wyższej jakości, ale niestety mogę wybrać tylko jedną najlepszą. Odpowiedź Normana była szczegółowa i użyteczna, wyjaśniając rzeczy w sposób, który pokazał niektóre teoretyczne podstawy, forall
a jednocześnie pokazując niektóre praktyczne implikacje tego. odpowiedź yairchuobejmował obszar, o którym nikt inny nie wspomniał (zmienne typu o zasięgu) i zilustrował wszystkie pojęcia kodem i sesją GHCi. Gdyby możliwe było wybranie obu najlepiej, zrobiłbym to. Niestety nie mogę i po dokładnym przyjrzeniu się obu odpowiedziom zdecydowałem, że yairchu nieznacznie przewyższa Normana z powodu ilustracyjnego kodu i dołączonego wyjaśnienia. Jest to jednak trochę niesprawiedliwe, ponieważ naprawdę potrzebowałem obu odpowiedzi, aby zrozumieć to do tego stopnia, że forall
nie odczuwam lekkiego strachu, gdy widzę to w podpisie typu.
Odpowiedzi:
Zacznijmy od przykładu kodu:
Ten kod nie kompiluje się (błąd składniowy) w zwykłym Haskell 98. Wymaga rozszerzenia do obsługi
forall
słowa kluczowego.Zasadniczo istnieją 3 różne typowe zastosowania dla
forall
słowa kluczowego (lub przynajmniej tak wydaje ), a każda z nich ma swoje własne rozszerzenia Haskell:ScopedTypeVariables
,RankNTypes
/Rank2Types
,ExistentialQuantification
.W powyższym kodzie nie pojawia się błąd składniowy przy żadnej z tych opcji, ale sprawdzane są tylko przy
ScopedTypeVariables
włączonej.Zmienne typu o zasięgu:
Zmienne typu o zasięgu pomagają określić typy kodu wewnątrz
where
klauzul. To sprawia, żeb
wval :: b
tym samym cob
wfoob :: forall a b. (b -> b) -> b -> (a -> b) -> Maybe a -> b
.Zagmatwany punkt : możesz usłyszeć, że gdy pominiesz
forall
typ, jest on nadal niejawnie obecny. ( z odpowiedzi Normana: „zwykle te języki pomijają forall z typów polimorficznych” ). To twierdzenie jest prawidłowe, ale odnosi się do innych zastosowańforall
, a nie doScopedTypeVariables
wykorzystania.Rodzaje rangi N:
Zacznijmy od tego, że
mayb :: b -> (a -> b) -> Maybe a -> b
jest to równoważnemayb :: forall a b. b -> (a -> b) -> Maybe a -> b
, z wyjątkiem sytuacji, gdyScopedTypeVariables
jest włączony.Oznacza to, że działa dla każdego
a
ib
.Powiedzmy, że chcesz zrobić coś takiego.
Jaki musi być tego rodzaj
liftTup
? JestliftTup :: (forall x. x -> f x) -> (a, b) -> (f a, f b)
. Aby zobaczyć dlaczego, spróbujmy to zakodować:„Hmm… dlaczego GHC wnioskuje, że krotka musi zawierać dwa tego samego typu? Powiedzmy, że nie muszą”
Hmm więc tutaj GHC nie pozwala nam zastosować
liftFunc
nav
bov :: b
iliftFunc
chcex
. Naprawdę chcemy, aby nasza funkcja otrzymała funkcję, która akceptuje każdą możliwąx
!Więc to nie
liftTup
działa dla wszystkichx
, to funkcja, którą dostaje, która działa.Istnienie ilościowe:
Użyjmy przykładu:
Czym różni się to od typów Rank-N?
Dzięki typom rang N
forall a
oznaczało, że twoje wyrażenie musi pasować do wszystkich możliwycha
s. Na przykład:Pusta lista działa jak lista dowolnego typu.
Tak więc w przypadku Existential-Quantification,
forall
s wdata
definicjach oznacza, że zawarta wartość może być dowolnego odpowiedniego typu, a nie że musi być wszystkich odpowiednich typów.źródło
ScopedTypeVariables
wydajesz się gorszy niż jest. Jeśli napiszesz typb -> (a -> b) -> Maybe a -> b
z tym rozszerzeniem, nadal będzie dokładnie taki sam jakforall a b. b -> (a -> b) -> Maybe a -> b
. Jednakże, jeśli chcesz zapoznać się samob
(i nie ma to niejawnie ilościowo) następnie trzeba napisać wersję wyraźnie ilościowo. W przeciwnym razieSTV
byłoby to bardzo nachalne rozszerzenie.ScopedTypeVariables
i nie sądzę, że to źle. imho jest to bardzo pomocne narzędzie w procesie programowania, a zwłaszcza dla początkujących w Haskell, i jestem wdzięczny, że istnieje.Nie. (Może Don Stewart może.)
Oto bariery dla prostego, jasnego wyjaśnienia lub
forall
:To kwantyfikator. Musisz mieć przynajmniej trochę logiki (rachunek predykatów), aby zobaczyć uniwersalny lub egzystencjalny kwantyfikator. Jeśli nigdy nie widziałeś rachunku predykatu lub nie znasz się na kwantyfikatorach (a widziałem studentów, którzy nie czują się komfortowo podczas egzaminów kwalifikacyjnych), to nie ma dla ciebie łatwego wyjaśnienia
forall
.To kwantyfikator typu . Jeśli nie widziałeś Systemu F i ćwiczyłeś pisanie typów polimorficznych, będziesz
forall
mylić. Doświadczenie z Haskellem lub ML nie jest wystarczające, ponieważ zwykle te języki pomijająforall
typy polimorficzne. (Moim zdaniem jest to błąd w projektowaniu języka).W szczególności w Haskell
forall
jest używany w sposób, który wydaje mi się mylący. (Nie jestem teoretykiem typów, ale moja praca styka się z wieloma teoriami typów i całkiem nieźle się z tym czuję). Dla mnie głównym źródłem nieporozumień jestforall
kodowanie typu, który Sam wolałbym pisaćexists
. Jest to uzasadnione trudnym izomorfizmem typu, obejmującym kwantyfikatory i strzałki, i za każdym razem, gdy chcę to zrozumieć, muszę to sprawdzić i sam opracować izomorfizm.Jeśli nie podoba ci się pomysł na izomorfizm typu, lub jeśli nie masz praktyki w myśleniu o izomorfizmie typu, to użycie go
forall
będzie ci przeszkadzać.Podczas gdy ogólna koncepcja
forall
jest zawsze taka sama (zobowiązanie do wprowadzenia zmiennej typu), szczegóły różnych zastosowań mogą się znacznie różnić. Nieformalny angielski nie jest bardzo dobrym narzędziem do wyjaśniania różnic. Aby naprawdę zrozumieć, co się dzieje, potrzebujesz trochę matematyki. W tym przypadku odpowiednią matematykę można znaleźć we wprowadzającym tekście Benjamina Pierce'a Typy i języki programowania , który jest bardzo dobrą książką.Jeśli chodzi o twoje konkretne przykłady,
runST
powinien sprawić, że głowa cię boli. Typy wyższego rzędu (wszystkie po lewej stronie strzałki) rzadko występują na wolności. Zachęcam do przeczytania artykułu, który przedstawiłrunST
: „Leniwe wątki stanu funkcjonalnego” . To naprawdę dobry papier, który zapewni znacznie lepszą intuicję dla określonego typurunST
i ogólnie dla wyższych rang. Wyjaśnienie zajmuje kilka stron, jest bardzo dobrze zrobione i nie zamierzam tutaj go zagęszczać.Rozważać
Jeśli zadzwonię
bar
, mogę po prostu wybrać dowolny typ,a
który mi się podoba, i mogę przekazać mu funkcję od typua
do typua
. Na przykład mogę przekazać funkcję(+1)
lub funkcjęreverse
. Możesz pomyśleć o tym,forall
że mówi „Teraz mogę wybrać typ”. (Słowo techniczne określające typ jest tworzone .)Ograniczenia wywoływania
foo
są znacznie bardziej rygorystyczne: argumentemfoo
musi być funkcja polimorficzna. W przypadku tego typu jedynymi funkcjami, które mogę przekazać,foo
sąid
funkcje, które zawsze się różnią lub popełniają błędyundefined
. Powodem jest to, że zfoo
Theforall
znajduje się na lewo od strzałki, tak jak wywołującyfoo
I nie dostać się do piłki, coa
oznacza raczej, że to realizacja stanowifoo
, że dostaje się wybrać to, coa
jest. Ponieważforall
znajduje się po lewej stronie strzałki, a nie nad strzałką, jak wbar
, instancja ma miejsce w treści funkcji, a nie w miejscu wywołania.Podsumowanie: kompletne wyjaśnienie
forall
kluczowych wymaga matematyki i może być rozumiane tylko przez kogoś, kto studiował matematykę. Nawet częściowe wyjaśnienia są trudne do zrozumienia bez matematyki. Ale może moje częściowe, nie matematyczne wyjaśnienia trochę pomogą. Czytaj dalej Launchbury i Peyton Jones dalejrunST
!Dodatek: żargon „powyżej”, „poniżej”, „po lewej stronie”. Nie mają one nic wspólnego z tekstowym sposobem pisania typów i wszystkim, co dotyczy drzew o składni abstrakcyjnej. W składni abstrakcyjnej a
forall
przyjmuje nazwę zmiennej typu, a następnie występuje pełny typ „poniżej” forall. Strzałka przyjmuje dwa typy (typ argumentu i wynik) i tworzy nowy typ (typ funkcji). Typ argumentu znajduje się „po lewej stronie” strzałki; jest to lewe dziecko strzałki w drzewie składni abstrakcyjnej.Przykłady:
W
forall a . [a] -> [a]
forall znajduje się nad strzałą; co jest po lewej stronie strzałki[a]
.W
typ w nawiasach nazywa się „forall na lewo od strzałki”. (Używam tego typu w optymalizatorze, nad którym pracuję).
źródło
forall a . [a] -> [a]
, że forall znajduje się po lewej stronie strzałki.forall
w tych okolicznościach, jak skutecznie linii hałas. Przejrzę dokument, do którego linkujesz (dziękuję również za link!) I zobaczę, czy jest on w moim zrozumieniu. Sława.exists
nigdy nie został wdrożony. (To nie jest część systemu F!) W Haskell część systemu F jest domniemana, aleforall
jest jedną z rzeczy, których nie można całkowicie zmieść pod dywan. To tak, jakby zaczęli od Hindley-Milnera, co pozwoliłobyforall
na domniemanie, a następnie wybrali mocniejszy system typów, myląc tych z nas, którzy badali „forall” i „istnieje” FOL-a i tam się zatrzymali.Moja oryginalna odpowiedź:
Jak wskazuje Norman, bardzo trudno jest podać jasne, jasne angielskie wyjaśnienie terminu technicznego z teorii typów. Wszyscy jednak próbujemy.
Jest tak naprawdę tylko jedna rzecz do zapamiętania o „forall”: wiąże typy z pewnym zakresem . Kiedy to zrozumiesz, wszystko jest dość łatwe. Jest to odpowiednik „lambda” (lub forma „let”) na poziomie czcionki - Norman Ramsey używa pojęcia „lewo” / „powyżej”, aby przekazać tę samą koncepcję zakresu w swojej doskonałej odpowiedzi .
Większość zastosowań „forall” jest bardzo prosta i można je znaleźć w Podręczniku użytkownika GHC, S7.8 ., Szczególnie doskonałe S7.8.5 na zagnieżdżonych formach „forall”.
W Haskell zwykle pomijamy spoiwo dla typów, gdy typ jest powszechnie kwanitowany, tak jak:
jest równa:
Otóż to.
Ponieważ możesz teraz powiązać zmienne typu z pewnym zakresem, możesz mieć zakresy inne niż najwyższy poziom („ uniwersalnie kwantyfikowany ”), podobnie jak twój pierwszy przykład, w którym zmienna typu jest widoczna tylko w strukturze danych. Pozwala to na ukryte typy („ typy egzystencjalne ”). Lub możemy mieć dowolne zagnieżdżanie wiązań („typy rangi N”).
Aby głęboko zrozumieć systemy typów, musisz nauczyć się żargonu. Taka jest natura informatyki. Jednak proste zastosowania, takie jak powyżej, powinny być możliwe do zrozumienia intuicyjnie, poprzez analogię z „let” na poziomie wartości. Świetnym wprowadzeniem jest Launchbury i Peyton Jones .
źródło
length :: forall a. [a] -> Int
nie jest równoważne zlength :: [a] -> Int
momentemScopedTypeVariables
włączenia. Kiedyforall a.
tam jest, wpływalength
„swhere
klauzuli (jeśli posiada) i zmienia znaczenie zmiennych typu wymienionycha
w nim.Eee, a co z prostą logiką pierwszego rzędu?
forall
całkiem wyraźnie odnosi się do uniwersalnej kwantyfikacji iw tym kontekście termin egzystencjalny ma również większy sens, chociaż byłoby mniej niezręczne, gdyby istniałoexists
słowo kluczowe. To, czy kwantyfikacja jest rzeczywiście uniwersalna czy egzystencjalna, zależy od umiejscowienia kwantyfikatora w zależności od tego, gdzie zmienne są używane po której stronie strzałki funkcji, a wszystko to jest nieco mylące.Tak więc, jeśli to nie pomoże, lub jeśli po prostu nie lubią logiki symbolicznej, z bardziej funkcjonalnych programowania-owski perspektywie można myśleć zmiennych typu jako po prostu (implicite) typu parametry do funkcji. Funkcje przyjmujące parametry typu w tym sensie są tradycyjnie pisane za pomocą dużej lambda z dowolnego powodu, który napiszę tutaj jako
/\
.Rozważmy więc
id
funkcję:Możemy przepisać go jako lambdas, przenosząc „parametr typu” poza podpis typu i dodając wbudowane adnotacje typu:
To samo zrobiono, aby
const
:Więc twój
bar
funkcja może wyglądać mniej więcej tak:Zauważ, że typ funkcji został podany
bar
jako argument zależy odbar
parametru type. Zastanów się, czy zamiast tego masz coś takiego:Oto
bar2
zastosowanie funkcji do czegoś typuChar
, więc podaniebar2
dowolnego parametru typu innego niżChar
spowoduje błąd typu.Z drugiej strony, oto co
foo
może wyglądać:W przeciwieństwie do tego
bar
,foo
wcale nie przyjmuje żadnych parametrów typu! Pełni tę samą funkcję przyjmuje parametr typu, a następnie stosuje tę funkcję do dwóch różnych typów.Więc kiedy widzisz
forall
w podpisie typu, pomyśl o tym jak o wyrażeniu lambda dla podpisów typu . Podobnie jak zwykłe lambdy, zakresforall
rozszerzeń jest możliwie najdalej w prawo, aż do nawiasów okrągłych, i podobnie jak zmienne powiązane w zwykłej lambdzie, zmienne typu powiązane przezforall
znajdują się w zakresie wyrażenia kwantyfikowanego.Post Scriptum : Być może możesz się zastanawiać - skoro myślimy o funkcjach przyjmujących parametry typu, dlaczego nie możemy zrobić z tymi parametrami czegoś bardziej interesującego niż umieścić je w podpisie typu? Odpowiedź jest taka, że możemy!
Funkcja, która łączy zmienne typu z etykietą i zwraca nowy typ, jest konstruktorem typów , który można napisać w ten sposób:
Potrzebowalibyśmy jednak zupełnie nowej notacji, ponieważ sposób zapisu takiego typu jest podobny
Either a b
sugeruje już „zastosowanie funkcjiEither
do tych parametrów”.Z drugiej strony funkcja, która niejako „dopasowuje wzorzec” parametrów parametrów typu, zwracając różne wartości dla różnych typów, jest metodą klasy typów . Nieznaczne rozszerzenie
/\
powyższej składni sugeruje coś takiego:Osobiście uważam, że wolę właściwą składnię Haskella ...
Funkcja, która „dopasowuje” parametry swojego typu i zwraca dowolny, istniejący typ jest rodziną typów lub zależnością funkcjonalną - w poprzednim przypadku nawet już wygląda bardzo podobnie do definicji funkcji.
źródło
λ
w tym przypadku, ale rozszerzenie składni GHC w Unicode nie obsługuje tego, ponieważ λ jest literą , niefortunny fakt, który hipotetycznie dotyczyłby również moich hipotetycznych abstrakcji wielkiej lambdy. Stąd/\
przez analogię do\
. Przypuszczam, że mogłem po prostu użyć,∀
ale starałem się unikać predykatu ...Oto krótkie i nieprzyzwoite wyjaśnienie w prostych słowach, które prawdopodobnie już znasz.
The
forall
kluczowe jest używane w Haskell tylko w jeden sposób. To zawsze oznacza to samo, kiedy go widzisz.Uniwersalna kwantyfikacja
Powszechnie ilościowo typ to typ formy
forall a. f a
. Wartość tego typu można traktować jako funkcję, która przyjmuje typa
jako argument i zwraca wartość typuf a
. Tyle że w Haskell argumenty tego typu są przekazywane niejawnie przez system typów. Ta „funkcja” musi dać ci tę samą wartość bez względu na to, jaki typ otrzyma, więc wartość jest polimorficzna .Rozważmy na przykład typ
forall a. [a]
. Wartość tego typu pobiera inny typa
i zwraca listę elementów tego samego typua
. Oczywiście istnieje tylko jedna możliwa implementacja. Musi dać ci pustą listę, ponieważa
może być absolutnie dowolnego typu. Pusta lista jest jedyną wartością listy, która jest polimorficzna w swoim typie elementu (ponieważ nie ma żadnych elementów).Lub typ
forall a. a -> a
. Program wywołujący taką funkcję podaje zarówno typ, jaka
i wartość typua
. Implementacja musi następnie zwrócić wartość tego samego typua
. Jest tylko jedna możliwa implementacja. Musiałby zwrócić taką samą wartość, jaką podano.Egzystencjalna kwantyfikacja
Egzystencjalnie ilościowo rodzaj miałoby formę
exists a. f a
, jeśli Haskell poparła ten zapis. Wartość tego typu można traktować jako parę (lub „produkt”) składającą się z typua
i wartości typuf a
.Na przykład, jeśli masz wartość typu
exists a. [a]
, masz listę elementów jakiegoś typu. Może to być dowolny typ, ale nawet jeśli nie wiesz, co to takiego, możesz wiele zrobić z taką listą. Możesz to odwrócić lub policzyć liczbę elementów lub wykonać dowolną inną operację na liście, która nie zależy od typu elementów.OK, poczekaj chwilę. Dlaczego Haskell używa
forall
określenia „egzystencjalnego” takiego jak poniżej?Może to być mylące, ale tak naprawdę opisuje typ konstruktora danych
SB
:Po zbudowaniu możesz myśleć o wartości typu
ShowBox
składającej się z dwóch rzeczy. Jest to typs
wraz z wartością typus
. Innymi słowy, jest to wartość egzystencjalnie skwantyfikowanego typu.ShowBox
można by naprawdę napisać takexists s. Show s => s
, jakby Haskell poparł ten zapis.runST
i przyjacieleBiorąc to pod uwagę, czym się różnią?
Weźmy najpierw
bar
. Pobiera typa
i funkcję typua -> a
i tworzy wartość typu(Char, Bool)
. Możemy wybraćInt
jakoa
i nadać muInt -> Int
na przykład funkcję typu . Alefoo
jest inaczej. Wymaga to, aby implementacjafoo
była w stanie przekazać dowolny typ, jaki chce, do funkcji, którą mu nadamy. Zatem jedyną funkcją, którą moglibyśmy rozsądnie nadać, jestid
.Powinniśmy teraz być w stanie zająć się znaczeniem rodzaju
runST
:runST
Musi więc być w stanie wygenerować wartość typua
, bez względu na to, jaki typ podamya
. Aby to zrobić, wykorzystuje argument typu,forall s. ST s a
który z pewnością musi jakoś wygenerowaća
. Co więcej, musi być w stanie wygenerować wartość typua
bez względu na typ, który implementacjarunST
decyduje się podać jakos
.Ok, i co? Zaletą jest to, że nakłada to ograniczenie na osobę dzwoniącą
runST
, ponieważ typa
nie może w ogóle obejmować typus
. Nie możeszST s [s]
na przykład przekazać wartości typu . W praktyce oznacza to, że implementacja umożliwiarunST
dowolną mutację o wartości typus
. Typ gwarantuje, że ta mutacja jest lokalna do wdrożeniarunST
.Typ
runST
jest przykładem typu polimorficznego rangi 2, ponieważ typ jego argumentu zawieraforall
kwantyfikator. Typfoo
powyższy ma również rangę 2. Zwykły typ polimorficzny, taki jak tegobar
, ma rangę 1, ale staje się rangą 2, jeśli typy argumentów muszą być polimorficzne, z własnymforall
kwantyfikatorem. A jeśli funkcja przyjmuje argumenty rangi 2, to jej typ to ranga 3 i tak dalej. Ogólnie rzecz biorąc, typ, który przyjmuje polimorficzne argumenty rangi,n
ma rangęn + 1
.źródło
Spróbuję wyjaśnić znaczenie i być może zastosowanie
forall
w kontekście Haskell i jego systemów typów.Zanim jednak zrozumiecie, że chciałbym skierować was do bardzo przystępnej i miłej rozmowy Runara Bjarnasona zatytułowanej „ Ograniczenia wyzwolenia, ograniczenia wolności ”. Dyskusja jest pełna przykładów z rzeczywistych przypadków użycia, a także przykładów w Scali na poparcie tego stwierdzenia, chociaż nie wspomina o tym
forall
. Spróbuję wyjaśnićforall
perspektywę poniżej.Bardzo ważne jest, aby przeczytać i uwierzyć w to oświadczenie, aby kontynuować następujące wyjaśnienie, więc zachęcam do obejrzenia rozmowy (przynajmniej jej części).
Obecnie bardzo powszechnym przykładem pokazującym ekspresję systemu typów Haskell jest podpis tego typu:
foo :: a -> a
Mówi się, że biorąc pod uwagę ten typ podpisu, istnieje tylko jedna funkcja, która może spełnić ten typ i jest to
identity
funkcja lub, co jest bardziej znaneid
.Na początku nauki Haskell zawsze zastanawiałem się nad poniższymi funkcjami:
oba spełniają powyższy podpis typu, dlaczego więc ludzie Haskell twierdzą, że
id
tylko on sam spełnia podpis typu?Wynika to z faktu, że
forall
ukryty podpis jest ukryty. Rzeczywisty typ to:Wróćmy teraz do stwierdzenia: Ograniczenia wyzwalają, ograniczenia wolności
Po przetłumaczeniu tego na system typów ta instrukcja staje się:
Ograniczenie na poziomie typu staje się swobodą na poziomie terminu
i
Wolność na poziomie typu staje się ograniczeniem na poziomie terminu
Spróbujmy udowodnić pierwsze stwierdzenie:
Ograniczenie na poziomie typu.
Więc nakładając ograniczenie na nasz podpis typu
staje się wolnością na poziomie terminów daje nam swobodę lub elastyczność w pisaniu tych wszystkich
To samo można zaobserwować ograniczając
a
dowolną inną klasę typów itpWięc teraz ten typ podpisu:
foo :: (Num a) => a -> a
tłumaczy to:Jest to znane jako kwantyfikacja egzystencjalna, co oznacza, że istnieją pewne przypadki,
a
dla których funkcja jest zasilana czymś typua
zwraca coś tego samego typu, a wszystkie te instancje należą do zbioru liczb.Dlatego widzimy, że dodanie ograniczenia (które
a
powinno należeć do zestawu liczb), uwalnia poziom terminu, aby mieć wiele możliwych implementacji.Teraz dochodzę do drugiego stwierdzenia, które faktycznie zawiera wyjaśnienie
forall
:Wolność na poziomie typu staje się ograniczeniem na poziomie terminu
Wyzwólmy teraz funkcję na poziomie typu:
To przekłada się na:
co oznacza, że implementacja tego typu podpisu powinna być taka,
a -> a
aby dotyczyła wszystkich okoliczności.Więc teraz zaczyna nas to ograniczać na poziomie terminu. Nie możemy już pisać
ponieważ ta implementacja nie byłaby satysfakcjonująca, gdybyśmy umieścili ją
a
jakoBool
.a
może być aChar
lub a[Char]
lub niestandardowym typem danych. We wszystkich okolicznościach powinien zwrócić coś podobnego typu. Ta swoboda na poziomie typu jest znana jako Universal Quantification, a jedyną funkcją, która może to zaspokoićktóry jest powszechnie znany jako
identity
funkcjaStąd
forall
jestliberty
na poziomie typu, którego rzeczywistym celem jestconstrain
określenie poziomu do konkretnej implementacji.źródło
Powodem tego, że to słowo kluczowe jest różne, jest to, że jest ono używane w co najmniej dwóch różnych rozszerzeniach systemu typów: typy wyższego rzędu i egzystencjalne.
Prawdopodobnie najlepiej jest po prostu przeczytać i zrozumieć te dwie rzeczy osobno, zamiast próbować wyjaśnić, dlaczego „forall” jest odpowiednią częścią składni jednocześnie.
źródło
Jak egzystencjalny egzystencjalny?
Wyjaśnienie, dlaczego
forall
wdata
definicjach są izomorficzne(exists a. a)
(pseudo-Haskell), można znaleźć w „Haskell / typy ilościowo określone” w Wikibooks .Oto krótkie, pełne podsumowanie:
MkT x
Jaki jest typ dopasowywania / dekonstrukcji wzorówx
?x
może być dowolnego typu (jak podano wforall
), więc jego typ to:Dlatego następujące są izomorficzne:
forall oznacza forall
Moją prostą interpretacją tego wszystkiego jest to, że „
forall
naprawdę oznacza„ dla wszystkich ””. Ważnym rozróżnieniem jest wpływforall
na definicję a zastosowanie funkcji .A
forall
oznacza definicję wartości lub funkcji musi być polimorficzna.Jeśli definiowana rzecz jest wartością polimorficzną , oznacza to, że wartość musi być poprawna dla wszystkich odpowiednich
a
, co jest dość restrykcyjne.Jeśli definiowana rzecz jest funkcją polimorficzną , oznacza to, że funkcja musi być poprawna dla wszystkich odpowiednich
a
, co nie jest aż tak restrykcyjne, ponieważ tylko dlatego, że funkcja jest polimorficzna, nie oznacza, że zastosowany parametr musi być polimorficzny. Oznacza to, że jeśli funkcja jest ważna dla wszystkicha
, to odwrotnie każdy odpowiednia
może być zastosowany do funkcji. Jednak typ parametru można wybrać tylko raz w definicji funkcji.Jeśli a
forall
jest w typie parametru funkcji (tj. ARank2Type
), oznacza to, że zastosowany parametr musi być naprawdę polimorficzny, aby być zgodny z ideą definicjiforall
środka jest polimorficzny. W takim przypadku typ parametru można wybrać więcej niż jeden raz w definicji funkcji ( „i jest wybierany przez implementację funkcji”, jak wskazał Norman )Dlatego powód egzystencjalne
data
definicje umożliwia dowolnya
dlatego konstruktor danych jest polimorficzny funkcja :rodzaj MkT ::
a -> *
Co oznacza,
a
że do funkcji można zastosować dowolny . W przeciwieństwie do, powiedzmy, wartości polimorficznej :rodzaj wartości T ::
a
Co oznacza, że definicja valueT musi być polimorficzna. W takim przypadku
valueT
można zdefiniować jako pustą listę[]
wszystkich typów.Różnice
Chociaż znaczenie dla
forall
jest spójne wExistentialQuantification
iRankNType
, egzystencjalność ma tę różnicę, żedata
konstruktor może być używany w dopasowywaniu wzorców. Jak udokumentowano w instrukcji obsługi ghc :źródło