Do czego służy słowo kluczowe "forall" w Haskell/GHC?

Zaczynam rozumieć, w jaki sposób słowo kluczowe forall 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, jak forall jest używany i po prostu nie mogę zawinąć mojego umysłu wokół jego użycia w takich rzeczach:

runST :: forall a. (forall s. ST s a) -> a

Lub wyjaśniając, dlaczego są one różne:

foo :: (forall a. a -> a) -> (Char,Bool)
bar :: forall a. ((a -> a) -> (Char, Bool))

Lub całe RankNTypes rzeczy...

[12]}wolę jasny, wolny od żargonu angielski, a nie rodzaje języka, które są normalne w środowiskach akademickich. Większość wyjaśnień, które próbuję przeczytać na ten temat (te, które mogę znaleźć w wyszukiwarkach) mają te problemy:
    Są niekompletne. Wyjaśniają one jedną część użycia tego słowa kluczowego (jak "typy egzystencjalne"), która sprawia, że czuję się szczęśliwy, dopóki nie przeczytam kodu, który używa go w zupełnie inny sposób (jak runST, foo i bar powyżej).
  1. są gęsto wypełnione założeniami, które czytałem ostatnio w dowolnej gałęzi matematyki dyskretnej, teorii kategorii lub algebra abstrakcyjna jest popularna w tym tygodniu. (Jeśli nigdy nie przeczytam słów "skonsultuj się z papierem cokolwiek Po szczegóły wdrożenia" ponownie, to będzie za wcześnie.)
  2. są napisane w sposób, który często zamienia nawet proste pojęcia w potwornie pokręconą i złamaną gramatykę i semantykę.

Więc...

Do pytania. Czy ktoś może całkowicie wyjaśnić słowo kluczowe forall w jasnym, prostym języku angielskim (lub, jeśli gdzieś istnieje, wskazać na takie jasne Wyjaśnienie, które mi umknęło), które nie zakłada, że jestem matematykiem przesiąkniętym żargonem?


Edited to add:

Były dwie wyróżniające się odpowiedzi z wyższej jakości poniżej, ale niestety mogę wybrać tylko jedną jako 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 i jednocześnie pokazał mi niektóre praktyczne implikacje tego. odpowiedź yairchu obejmowała obszar, o którym nikt inny nie wspomniał (zmienne typu scoped) i zilustrowała wszystkie pojęcia kodem i sesją GHCi. Gdyby można było wybrać oba jako najlepsze, zrobiłbym to. Niestety nie mogę i po dokładnym przyjrzeniu się obu odpowiedziom zdecydowałem, że yairchu jest nieco lepszy od Normana ze względu na kod ilustracyjny i załączone Wyjaśnienie. Jest to jednak trochę niesprawiedliwe, ponieważ naprawdę potrzebowałem obu odpowiedzi, aby to zrozumieć do tego stopnia, że Nie zostawia mnie z lekkim strachem, gdy widzę to w podpisie.

Author: Community, 2010-06-18

8 answers

Zacznijmy od przykładu kodu:

foob :: forall a b. (b -> b) -> b -> (a -> b) -> Maybe a -> b
foob postProcess onNothin onJust mval =
    postProcess val
    where
        val :: b
        val = maybe onNothin onJust mval

Ten kod nie kompiluje się (błąd składni) w zwykłym Haskell 98. Wymaga rozszerzenia do obsługi słowa kluczowego forall.

Zasadniczo istnieją 3 różne wspólne zastosowania dla forall słowa kluczowego (a przynajmniej tak to wydaje się ), a każdy z nich ma swoje własne rozszerzenie Haskell: ScopedTypeVariables, RankNTypes/Rank2Types, ExistentialQuantification.

Powyższy kod nie zawiera błędu składni przy żadnej z tych opcji, a jedynie sprawdza typ z ScopedTypeVariables włączone.

Zmienne Typu Scoped:

Zmienne typu Scoped pomagają określić typy kodu wewnątrz where klauzul. To sprawia, że b w val :: b jest tym samym, co b w foob :: forall a b. (b -> b) -> b -> (a -> b) -> Maybe a -> b.

mylący punkt: możesz usłyszeć, że gdy pominiesz forall z Typu, to w rzeczywistości nadal tam jest. (Z odpowiedzi Normana: "normalnie te języki pomijają forall z typów polimorficznych" ). To twierdzenie jest poprawne, ale to odnosi się do innych zastosowań forall, a nie do zastosowania ScopedTypeVariables.

Rank-N-Typ:

Zacznijmy od tego mayb :: b -> (a -> b) -> Maybe a -> b jest równoważne mayb :: forall a b. b -> (a -> b) -> Maybe a -> b, Z wyjątkiem, gdy ScopedTypeVariables jest włączone.

Oznacza to, że działa dla każdego a i b.

Powiedzmy, że chcesz zrobić coś takiego.
ghci> let putInList x = [x]
ghci> liftTup putInList (5, "Blah")
([5], ["Blah"])

Jaki musi być Typ tego liftTup? liftTup :: (forall x. x -> f x) -> (a, b) -> (f a, f b). Aby zobaczyć dlaczego, spróbujmy go zakodować:

ghci> let liftTup liftFunc (a, b) = (liftFunc a, liftFunc b)
ghci> liftTup (\x -> [x]) (5, "Hello")
    No instance for (Num [Char])
    ...
ghci> -- huh?
ghci> :t liftTup
liftTup :: (t -> t1) -> (t, t) -> (t1, t1)

"Hmm.. dlaczego GHC wnioskuje, że krotka musi zawierać dwie tego samego typu? Powiedzmy, że nie muszą być"[43]}

-- test.hs
liftTup :: (x -> f x) -> (a, b) -> (f a, f b)
liftTup liftFunc (t, v) = (liftFunc t, liftFunc v)

ghci> :l test.hs
    Couldnt match expected type 'x' against inferred type 'b'
    ...

Hmm. więc tutaj ghc nie pozwala nam zastosować liftFunc na v, ponieważ v :: b i liftFunc chce x. Naprawdę chcemy, aby nasza funkcja otrzymała funkcję, która akceptuje wszelkie możliwe x!

{-# LANGUAGE RankNTypes #-}
liftTup :: (forall x. x -> f x) -> (a, b) -> (f a, f b)
liftTup liftFunc (t, v) = (liftFunc t, liftFunc v)

Więc to nie liftTup Działa dla wszystkich x, to funkcja, którą dostaje, która robi.

Kwantyfikacja Egzystencjalna:

Użyjmy przykład:

-- test.hs
{-# LANGUAGE ExistentialQuantification #-}
data EQList = forall a. EQList [a]
eqListLen :: EQList -> Int
eqListLen (EQList x) = length x

ghci> :l test.hs
ghci> eqListLen $ EQList ["Hello", "World"]
2
Czym to się różni od Rank-N-typów?
ghci> :set -XRankNTypes
ghci> length (["Hello", "World"] :: forall a. [a])
    Couldnt match expected type 'a' against inferred type '[Char]'
    ...

Z Rank-N-typów, forall a oznaczało, że Twoje wyrażenie musi pasować do wszystkich możliwycha s. na przykład:

ghci> length ([] :: forall a. [a])
0

Pusta lista działa jako lista dowolnego typu.

Więc z Kwantyfikacją egzystencjalną, forallS w data definicjach oznaczają, że wartość zawartamoże byćdowolnego odpowiedniego typu, nie żemusi {48]} być wszystkich {48]} odpowiednich typów.

 227
Author: yairchu,
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
2017-05-23 11:54:56

Czy ktoś może całkowicie wyjaśnić słowo kluczowe forall w jasnym, prostym języku angielskim?

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ć kwantyfikator uniwersalny lub egzystencjalny. Jeśli nigdy nie widziałeś rachunku predykatów lub nie czujesz się komfortowo z kwantyfikatorami (I widziałem studentów podczas egzaminów doktoranckich, którzy nie są wygodne), to dla Ciebie nie ma łatwego wyjaśnienia forall.

  • Jest to Typ kwantyfikator. Jeśli nie widziałeś systemu F i nauczyłeś się pisać typy polimorficzne, znajdziesz forall mylące. Doświadczenie z Haskell lub ML nie wystarcza, ponieważ normalnie języki te pomijają forall z typów polimorficznych. (Moim zdaniem jest to projekt językowy błąd.)

  • W Haskell w szczególności, forall jest używany w sposób, który uważam za mylący. (Nie jestem teoretykiem typu, ale moja praca przynosi mi kontakt z partii teorii typu, i czuję się z tym całkiem komfortowo.) Dla mnie głównym źródłem zamieszania jest to, że forall jest używany do kodowania typu, który sam wolałbym napisać z exists. Uzasadnia to skomplikowany izomorfizm typu obejmujący kwantyfikatory i strzałki. za każdym razem, gdy chcę go zrozumieć, mam aby sprawdzić rzeczy i wypracować izomorfizm siebie.

    Jeśli nie czujesz się komfortowo z ideą izomorfizmu typu, lub jeśli nie masz żadnej praktyki myślenia o izomorfizmach typu, to użycie forall będzie cię zniechęcić.

  • Chociaż ogólne pojęcie forall jest zawsze takie samo( wiąże się z wprowadzeniem zmiennej typu), szczegóły różnych zastosowań mogą się znacznie różnić. Nieformalny angielski nie jest zbyt dobrym narzędziem do wyjaśnienia różnic. Do naprawdę zrozum o co chodzi, potrzebujesz trochę matematyki. W tym przypadku odpowiednią matematykę można znaleźć w tekście wprowadzającym Benjamina Pierce ' a typy i języki programowania , który jest bardzo dobrą książką.

Jeśli chodzi o twoje konkretne przykłady,

  • runST powinno cię boleć głowa. Typy wyższej rangi (forall na lewo od strzałki) są rzadko spotykane na wolności. Zachęcam do zapoznania się z artykułem, który wprowadził runST: "Lazy Functional State Threads". Jest to naprawdę dobry artykuł, który da ci znacznie lepszą intuicję dla typu runST w szczególności i dla typów wyższych rang w ogóle. Wyjaśnienie zajmuje kilka stron, jest bardzo dobrze zrobione, i nie będę próbował skondensować go tutaj.

  • Rozważ

    foo :: (forall a. a -> a) -> (Char,Bool)
    bar :: forall a. ((a -> a) -> (Char, Bool))
    

    Jeśli wywołam bar, mogę po prostu wybrać dowolny typ a, który mi się podoba, i mogę przekazać mu funkcję z type a do type a. Na przykład może przekazać funkcję (+1) lub funkcję reverse. Możesz myśleć o forall jako o powiedzeniu "teraz mogę wybrać typ". (Techniczne słowo określające typ to instantiating .)

    Ograniczenia wywołania foo są znacznie bardziej rygorystyczne: argument do foo musi być funkcją polimorficzną. W przypadku tego typu, jedynymi funkcjami, które mogę przekazać fooid lub funkcja, która zawsze się rozchodzi lub błędy, jak undefined. Powodem jest to, że z foo, forall jest na lewo od strzałki, więc jako rozmówca foo nie mogę wybrać tego, czym jest a-raczej to implementacja {42]} z foo, która wybiera to, czym jest a. Ponieważ forall znajduje się na lewo od strzałki, A nie nad strzałką, jak w bar, instancjacja odbywa się w ciele funkcji, a nie w miejscu wywołania.

Podsumowanie: a pełne Wyjaśnienie słowa kluczowego forall wymaga matematyki i może być zrozumiałe 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ą. Idź poczytać Launchbury i Peyton Jones na runST!


Dodatek: żargon "powyżej"," poniżej","na lewo od". Nie mają one nic wspólnego ze sposobami pisania typów tekstowych i wszystko, co ma związek z drzewami abstrakcyjno-składni. W składni abstrakcyjnej forall przyjmuje nazwę zmiennej typu, A następnie jest pełny Typ" poniżej " forall. Strzałka przyjmuje dwa typy (argument i typ wyniku) i tworzy nowy typ (typ funkcji). Typ argumentu jest" na lewo od " strzałki; jest lewym potomkiem strzałki w drzewie abstrakcyjnej składni.

Przykłady:

  • W forall a . [a] -> [a], forall znajduje się nad strzałką; to, co znajduje się po lewej stronie strzałki, to [a].

  • W

    forall n f e x . (forall e x . n e x -> f -> Fact x f) 
                  -> Block n e x -> f -> Fact x f
    

    Typ w nawiasach będzie nazywany " a forall po lewej stronie arrow". (Używam takich typów w optymalizatorze, nad którym pracuję.)

 104
Author: Norman Ramsey,
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
2014-12-09 17:00:39

Moja oryginalna odpowiedź:

Czy ktoś może całkowicie wyjaśnić słowo kluczowe forall w jasnym, prostym języku angielskim

Jak wskazuje Norman, bardzo trudno jest dać jasne, proste angielskie Wyjaśnienie termin techniczny z teorii typów. Wszyscy się staramy.

Jest tylko jedna rzecz do zapamiętania o 'forall': wiąże typy z niektóre zakres . Kiedy to zrozumiesz, wszystko jest dość łatwe. Jest to odpowiednik 'lambda' (lub forma 'let') na poziomie typu-Norman Ramsey używa pojęcia"Left"/ "above", aby przekazać to samo pojęcie zakresu w jego doskonała odpowiedź .

Większość zastosowań 'forall' jest bardzo prosta i można je znaleźć w the GHC Users Manual, S7. 8., szczególnie doskonały S7.8. 5 na zagnieżdżonym formy "forall".

W Haskell zwykle zostawiamy spoiwo dla typów, gdy typ jest ogólnie rzecz biorąc, tak:

length :: forall a. [a] -> Int

Jest równoważne do:

length :: [a] -> Int
To wszystko.

Ponieważ możesz teraz powiązać zmienne typu z pewnym zakresem, możesz mieć zakresy inne niż najwyższy poziom (" "), jak w pierwszym przykładzie, gdzie zmienna typu jest widoczna tylko w strukturze danych. Pozwala to na dla typów ukrytych ("typy egzystencjalne"). Albo możemy mieć dowolne zagnieżdżanie wiązań ("rangi N typów").

Aby dogłębnie zrozumieć systemy typów, musisz nauczyć się żargonu. To natura informatyki. Jednak proste zastosowania, jak wyżej, powinny być możliwe do uchwycenia intuicyjnie, poprzez analogię z "let" na poziomie wartości. A wielkie Wprowadzenie To Launchbury i Peyton Jones.

 44
Author: Don Stewart,
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
2017-05-23 12:34:32
Są gęsto wypełnione założeniami, które przeczytałem w najnowszych dziedzinach matematyki dyskretnej, teorii kategorii czy algebry abstrakcyjnej, które są popularne w tym tygodniu. (JeĹ "li Juĺľ nigdy nie przeczytam sĹ 'Ăłw" skonsultuj siÄ ™ z papierem po szczegóŠ'y realizacji", bÄ ™ dzie to za szybko.)
A co z prostą logiką pierwszego rzędu? forall jest dość wyraźnie w odniesieniu do kwantyfikacji uniwersalnej i w tym kontekście termin egzystencjalny sprawia, że więcej sensu, choć byłoby mniej niezręcznie, gdyby było słowo kluczowe exists. To, czy kwantyfikacja jest faktycznie uniwersalna, czy egzystencjalna, zależy od położenia kwantyfikatora względem miejsca, w którym zmienne są używane, po której stronie strzałki funkcji i to wszystko jest nieco mylące.

Więc, jeśli to nie pomaga, lub jeśli po prostu nie lubisz logiki symbolicznej, z bardziej funkcjonalnego punktu widzenia programowania można myśleć o zmiennych typu jako o byciu (implicit) wpisz parametry do funkcji. Funkcje przyjmujące parametry typu w tym sensie są tradycyjnie zapisywane za pomocą wielkiej lambda z jakiegokolwiek powodu, co napiszę tutaj jako /\.

Rozważmy więc id funkcję:

id :: forall a. a -> a
id x = x
Możemy go przepisać jako lambda, przenosząc "parametr typu" z podpisu typu i dodając adnotacje typu inline:
id = /\a -> (\x -> x) :: a -> a

To samo zrobiono const:

const = /\a b -> (\x y -> x) :: a -> b -> a

Więc twoja bar funkcja może być coś takiego:

bar = /\a -> (\f -> ('t', True)) :: (a -> a) -> (Char, Bool)

Zauważ, że typ funkcji podanej bar jako argument zależy od parametru typu bar. Zastanów się, czy zamiast tego masz coś takiego: {]}

bar2 = /\a -> (\f -> (f 't', True)) :: (a -> a) -> (Char, Bool)

Tutaj bar2 jest zastosowanie funkcji do czegoś typu Char, więc podanie bar2 dowolnego parametru typu innego niż Char spowoduje błąd typu.

Z drugiej strony, oto jak foo może wyglądać:

foo = (\f -> (f Char 't', f Bool True))

bar, foo Nie przyjmuje żadnego rodzaju parametry w ogóle! Przyjmuje funkcję, która sama pobiera 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 jako o wyrażeniu lambda dla podpisów typu. Podobnie jak zwykłe lambda, zakres forall rozciąga się tak daleko w prawo, jak to możliwe, aż do zamkniętego nawiasu, i tak jak zmienne związane w regularnej lambda, zmienne typu związane przez forall są tylko w zakres w wyrażeniu kwantyfikowanym.


Post scriptum: być może zastanawiasz się-teraz, gdy myślimy o funkcjach pobierających parametry typu, Dlaczego nie możemy zrobić czegoś bardziej interesującego z tymi parametrami niż umieścić je w podpisie typu? Odpowiedź jest taka, że możemy!

Funkcja, która umieszcza zmienne typu razem z etykietą i zwraca nowy typ, to Konstruktor typu , który można napisać coś w stylu to:

Either = /\a b -> ...

Ale potrzebujemy zupełnie nowej notacji, ponieważ sposób zapisu takiego typu, jak Either a b, sugeruje już "zastosuj funkcję Either do tych parametrów".

Z drugiej strony, funkcja, która rodzaj "wzorca dopasowuje" do swoich parametrów typu, zwracając różne wartości dla różnych typów, jest metodą klasy typu. Małe rozszerzenie mojej składni /\ powyżej sugeruje coś takiego:

fmap = /\ f a b -> case f of
    Maybe -> (\g x -> case x of
        Just y -> Just b g y
        Nothing -> Nothing b) :: (a -> b) -> Maybe a -> Maybe b
    [] -> (\g x -> case x of
        (y:ys) -> g y : fmap [] a b g ys 
        []     -> [] b) :: (a -> b) -> [a] -> [b]

Osobiście myślę, że preferuj rzeczywistą składnię Haskella...

Funkcja, która "pattern dopasowuje" swoje parametry typu i zwraca dowolny, istniejący typ jest rodziną typów lub zależnością funkcyjną - w pierwszym przypadku nawet wygląda bardzo podobnie do definicji funkcji.

 25
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
2010-06-18 17:55:36

Oto szybkie i brudne wyjaśnienie w prostych słowach, które prawdopodobnie już znasz.

Słowo kluczowe forall jest tak naprawdę używane tylko w jeden sposób w Haskell. To zawsze oznacza to samo, kiedy to widzisz.

Kwantyfikacja uniwersalna

A typ Uniwersalny jest typem postaci forall a. f a. Wartość tego typu może być traktowana jako funkcja {[67] } przyjmująca Typ a jako argument i zwraca wartość typu f a. Z tym wyjątkiem, że w Haskell te argumenty typu są przekazywane domyślnie przez system typu. Ta "funkcja" musi dawać tę samą wartość bez względu na typ, który otrzymuje, więc wartość jest polimorficzna .

Na przykład, rozważmy typ forall a. [a]. Wartość tego typu pobiera inny typ a i zwraca listę elementów tego samego typu a. Jest oczywiście tylko jedna możliwa realizacja. To musiałoby dać ci pustą listę ponieważ a może być absolutnie każdy typ. Pusta lista jest jedyną wartością listy, która jest polimorficzna w swoim typie elementu (ponieważ nie ma elementów).

Lub typu forall a. a -> a. Wywołanie takiej funkcji zapewnia zarówno typ a, jak i wartość typu a. Implementacja musi zwrócić wartość tego samego typu a. Jest tylko jedna możliwa realizacja. Musiałby zwrócić tę samą wartość, którą otrzymał.

Egzystencjalny kwantyfikacja

Typkwantyfikowany egzystencjalnie miałby postać exists a. f a, gdyby Haskell wspierał tę notację. Wartość tego typu może być traktowana jako para (lub "produkt") składająca się z typu a i wartości typu f a.

Na przykład, jeśli masz wartość typu exists a. [a], masz listę elementów pewnego typu. Może to być dowolny typ, ale nawet jeśli nie wiesz, co to jest, Możesz wiele zrobić z taką listą. Mógłbyś odwróć go, możesz policzyć liczbę elementów lub wykonać dowolną inną operację na liście, która nie zależy od typu elementów.

/ Align = "left" / Dlaczego Haskell używa forall do oznaczenia typu "egzystencjalnego", jak poniżej?
data ShowBox = forall s. Show s => SB s

To może być mylące, ale tak naprawdę opisuje typ konstruktora danych SB:

SB :: forall s. Show s => s -> ShowBox

Po zbudowaniu można myśleć o wartości typu ShowBox jako składającej się z dwóch rzeczy. To typ s wraz z wartością typu s. Innymi słowy, jest to wartość typu kwantyfikowanego egzystencjalnie. ShowBox może być naprawdę zapisany jako exists s. Show s => s, jeśli Haskell poparł tę notację.

runST i przyjaciele

Biorąc pod uwagę to, czym się różnią?

foo :: (forall a. a -> a) -> (Char,Bool)
bar :: forall a. ((a -> a) -> (Char, Bool))

Weźmy najpierw bar. Przyjmuje typ a i funkcję typu a -> a i tworzy wartość typu (Char, Bool). Możemy wybrać Int jako a i nadać mu funkcję typu Int -> Int dla przykład. Ale to co innego. Wymaga to, aby implementacja foo była w stanie przekazać dowolny typ, jaki chce do danej funkcji. Więc jedyną funkcją, którą możemy rozsądnie mu dać, jest id.

Powinniśmy teraz być w stanie zmierzyć się ze znaczeniem typu runST:

runST :: forall a. (forall s. ST s a) -> a

Więc runST musi być w stanie wytworzyć wartość typu a, bez względu na to, jaki typ podamy jako a. Aby to zrobić, potrzebuje argumentu typu forall s. ST s a, który pod maską jest tylko funkcją typu forall s. s -> (a, s). Funkcja ta musi być w stanie wytworzyć wartość typu (a, s) bez względu na to, jakiego typu implementacja runST zdecyduje się podać jako s.

OK, i co z tego? Zaletą jest to, że stawia to ograniczenie na wywołujący runST w tym, że typ a nie może w ogóle obejmować typu s. Nie można na przykład przekazać jej wartości typu ST s [s]. W praktyce oznacza to, że implementacja {[27] } może swobodnie wykonywać mutacje o wartości typu s. Typ system gwarantuje, że mutacja ta jest lokalna dla implementacji runST.

Typ runST jest przykładem typu polimorficznego rank-2 , ponieważ Typ jego argumentu zawiera forall kwantyfikator. Typ foo powyżej również ma rangę 2. Zwykły Typ polimorficzny, podobnie jak bar, jest rangą-1, ale staje się rangą - 2, jeśli typy argumentów są wymagane, aby były polimorficzne, z własnym kwantyfikatorem forall. A jeśli funkcja przyjmuje argumenty rank-2, to jej Typ to ranga-3 i tak dalej. Ogólnie rzecz biorąc, typ, który przyjmuje argumenty polimorficzne rangi n ma rangę n + 1.

 20
Author: Apocalisp,
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
2016-02-22 17:36:27

Powodem, dla którego istnieją różne zastosowania tego słowa kluczowego, jest to, że jest ono używane w co najmniej dwóch różnych rozszerzeniach systemu typów: typy wyższej rangi i existentials.

Najlepiej jest po prostu poczytać i zrozumieć te dwie rzeczy osobno, zamiast próbować uzyskać wyjaśnienie, dlaczego 'forall' jest odpowiednim fragmentem składni w obu naraz.

 8
Author: ,
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
2010-06-18 17:26:12

Czy ktoś może całkowicie wyjaśnić słowo kluczowe forall w jasnym, prostym języku angielskim (lub, jeśli gdzieś istnieje, wskazać tak jasne wyjaśnienie, które przeoczyłem), że nie zakłada, że jestem matematykiem przesiąkniętym żargonem?

Postaram się wyjaśnić tylko znaczenie i być może zastosowanie forall w kontekście Haskell i jego systemów typu.

Ale zanim zrozumiesz, że chciałbym skierować Cię na bardzo przystępną i miłą rozmowę przez Runar Bjarnason zatytułował " ograniczenia wyzwalają, wolności ograniczają ". Dyskusja jest pełna przykładów z rzeczywistych przypadków użycia, jak również przykładów w Scali na poparcie tego stwierdzenia, chociaż nie wspomina forall. Postaram się wyjaśnić perspektywę forall poniżej.

                CONSTRAINTS LIBERATE, LIBERTIES CONSTRAIN

Bardzo ważne jest, aby przetrawić i uwierzyć w to stwierdzenie, aby przejść do następującego wyjaśnienia, więc zachęcam do obejrzenia rozmowy(przynajmniej jej części).

Teraz bardzo powszechny przykład, w systemie Haskell ' a jest to typ sygnatury:

foo :: a -> a

Mówi się, że biorąc pod uwagę ten typ, istnieje tylko jedna funkcja, która może zaspokoić ten typ i jest to identity funkcja lub co jest bardziej znane id.

W początkowych fazach nauki Haskella zawsze zastanawiałem się nad poniższymi funkcjami:]}
foo 5 = 6

foo True = False

Oba spełniają powyższą sygnaturę typu, to dlaczego ludzie Haskell twierdzą, że jest id sam, który spełnia podpis typu?

To dlatego, że w sygnaturze typu jest ukryty forall. Rzeczywisty typ to:

id :: forall a. a -> a
Wróćmy więc do stwierdzenia: ograniczenia wyzwalają, wolności ograniczają]}

Tłumacząc to na system typów, to stwierdzenie staje się:

Na poziomie typu ograniczenie staje się wolnością na poziomie terminowym.]}

I

Wolność na poziomie typu, staje się ograniczenie na poziomie terminowym


Spróbujmy udowodnić pierwsze stwierdzenie:]}

ograniczenie na poziomie typu..

Więc wprowadzenie ograniczenia na nasz Typ podpis
foo :: (Num a) => a -> a

staje się wolnością na poziomie terminowym daje nam swobodę lub elastyczność, aby napisać wszystkie te [36]}

foo 5 = 6
foo 4 = 2
foo 7 = 9
...

To samo można zaobserwować ograniczając a z innymi typeklasami itp

Więc teraz, co ten podpis typu: foo :: (Num a) => a -> a tłumaczy się na jest:

∃a , st a -> a, ∀a ∈ Num

Jest to znane jako kwantyfikacja egzystencjalna, co tłumaczy się na istnieją niektóre instancje a, dla których funkcja, gdy karmiona czymś typu a zwraca coś tego samego typu, a wszystkie te instancje należą do zbioru liczb.

Stąd widzimy dodanie ograniczenia (które a powinno należeć do zbioru liczb), uwalnia termin poziom, aby miał wiele możliwych implementacji.


Teraz nadchodzi druga twierdzenie i to, które faktycznie zawiera wyjaśnienie forall:

w zależności od tego, która z tych wartości jest większa, można ją zmienić na inną.]}

Więc teraz uwolnijmy funkcję na poziomie typu:

foo :: forall a. a -> a

Teraz to tłumaczy się na:

∀a , a -> a

Co oznacza, że implementacja tego typu podpisu powinna być taka, aby była a -> a dla wszystkich okoliczności.

Więc teraz to zaczyna nas ograniczać na poziomie terminowym. Nie możemy dłuższy zapis

foo 5 = 7

Ponieważ ta implementacja nie byłaby zadowalająca, gdybyśmy umieścili a jako Bool. a może być Char lub [Char] lub niestandardowy typ danych. W każdych okolicznościach powinien zwrócić coś podobnego. Ta swoboda na poziomie typu jest czymś, co jest znane jako kwantyfikacja uniwersalna, a jedyną funkcją, która może to zaspokoić, jest {36]}

foo a = a

Która jest powszechnie znana jako identity funkcja


Stąd forall jest liberty na poziomie typu, którego faktycznym celem jest constrain określenie poziomu do konkretnej realizacji.

 3
Author: Abhiroop Sarkar,
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
2018-04-22 22:35:04

Jak egzystencjalny jest egzystencjalny?

Z Kwantyfikacją egzystencjalną, foralls W data definicje oznaczają że wartość zawarta może być dowolnego odpowiedniego typu, nie że tomusi być zwszystkich odpowiednich typów. -- odpowiedź yachiru

W wikibooks można znaleźć wyjaśnienie, dlaczego forall w data definicje są izomorficzne do (exists a. a) (pseudo-Haskell) można znaleźć w "Haskell / Existentially quantified typy " .

Poniżej znajduje się krótkie, dosłowne podsumowanie:

data T = forall a. MkT a -- an existential datatype
MkT :: forall a. a -> T -- the type of the existential constructor

Kiedy dopasowujemy / dekonstruujemy MkT x, jaki jest typ x?

foo (MkT x) = ... -- -- what is the type of x?

x może być dowolnym typem (jak podano w forall), a więc jest to typ:

x :: exists a. a -- (pseudo-Haskell)

Zatem następujące są izomorficzne:

data T = forall a. MkT a -- an existential datatype
data T = MkT (exists a. a) -- (pseudo-Haskell)

Forall oznacza forall

Moja prosta interpretacja tego wszystkiego jest taka, że "forall naprawdę oznacza "dla wszystkich"". Ważnym wyróżnikiem jest wpływ forall na definicja kontra funkcja zastosowanie .

A forall oznacza definicja wartości lub funkcji musi być polimorficzna.

Jeśli zdefiniowana rzecz jest polimorficzną wartością , to oznacza, że wartość musi być ważna dla wszystkich odpowiednich a, co jest dość restrykcyjne.

Jeśli zdefiniowana rzecz jest funkcją polimorficzną , to oznacza, że funkcja musi być ważna dla wszystkich odpowiednich a, co nie jest tak restrykcyjne, ponieważ tylko dlatego, że funkcja jest polimorficzna, nie oznacza, że parametr zastosowany musi być polimorficzny. Oznacza to, że jeśli funkcja jest ważna dla wszystkich a, to odwrotnie Dowolna odpowiednia a może być zastosowana do funkcji. Jednak typ parametru może być wybrany tylko raz w definicji funkcji.

Jeśli a forall znajduje się wewnątrz typu parametru funkcji (tj. a Rank2Type), to oznacza zastosowany parametr musi być prawdziwie polimorficzny, aby był zgodny z ideą forall oznacza, że definicja jest polimorficzna. W tym przypadku typ parametru może być wybrany więcej niż jeden raz w definicji funkcji ( "i jest wybierany przez implementację funkcji", jak wskazał Norman )

Dlatego powód, dla którego egzystencjalne data definicje pozwalają dowolne a jest, ponieważ konstruktor danych jest polimorficznym funkcja :

MkT :: forall a. a -> T

Rodzaj MkT :: a -> *

Co oznacza, że dowolna a może być zastosowana do funkcji. W przeciwieństwie do, powiedzmy, polimorficznej wartości :

valueT :: forall a. [a]

Rodzaj wartości :: a

Co oznacza, że definicja valueT musi być polimorficzna. W tym przypadku {[31] } można zdefiniować jako pustą listę [] wszystkich typów.

[] :: [t]

Różnice

Mimo że znaczenie dla forall jest spójne w ExistentialQuantification i RankNType, existentials ma różnicę, ponieważ konstruktor data może być używany w dopasowywaniu wzorców. Jak udokumentowano w ghc user guide:

Gdy dopasowanie wzorca, każde dopasowanie wzorca wprowadza nowy, odrębny typ dla każdej zmiennej typu egzystencjalnego. Typy te nie mogą być unifikowane z żadnym innym typem, ani nie mogą uciec od zakresu dopasowania wzorca.

 2
Author: Louis Pan,
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
2017-05-23 12:34:32