Uczę się języka programowania Haskell i staram się owinąć głowę, jaka jest różnica między a type
a a kind
.
Jak rozumiem, a kind is a type of type
. Na przykład a ford is a type of car
i a car is a kind of vehicle
.
Czy to dobry sposób, aby o tym pomyśleć?
Ponieważ sposób, w jaki mój mózg jest obecnie okablowany, a ford is a **type** of car
także car is a **type** of vehicle
przez chwilę w tym samym czasie car is a **kind** of vehicle
. Tj. Warunki type
i kind
są wymienne.
Czy ktoś mógłby rzucić na to trochę światła?
type-theory
Thomas Cook
źródło
źródło
Odpowiedzi:
Tutaj „wartości”, „typy” i „rodzaje” mają znaczenie formalne, więc biorąc pod uwagę ich powszechne użycie w języku angielskim lub analogie do klasyfikacji samochodów, dotrzesz tylko do tej pory.
Moja odpowiedź dotyczy formalnego znaczenia tych terminów w kontekście Haskell; znaczenia te są oparte na (choć nie są tak naprawdę identyczne) znaczeniach używanych w matematycznej / CS „teorii typów”. Nie będzie to więc bardzo dobra odpowiedź „informatyczna”, ale powinna służyć jako całkiem dobra odpowiedź Haskella.
W języku Haskell (i innych językach) pomocne jest przypisanie typu do wyrażenia programu opisującego klasę wartości, jakie wyrażenie może posiadać. Zakładam, że widziałeś wystarczająco dużo przykładów, aby zrozumieć, dlaczego warto wiedzieć, że w wyrażeniu
sqrt (a**2 + b**2)
zmiennea
ib
zawsze będą wartościami typu,Double
a nie, powiedzmy,String
iBool
odpowiednio. Zasadniczo posiadanie typów pomaga nam pisać wyrażenia / programy, które będą działać poprawnie w szerokim zakresie wartości .Teraz możesz nie zdawać sobie sprawy, że typy Haskell, takie jak te, które pojawiają się w podpisach typów:
tak naprawdę są napisane w podrzędnym języku Haskell. Tekst programu
Functor f => (a -> b) -> f a -> f b
jest - dosłownie - wyrażeniem typu napisanym w tym języku podrzędnym. Podjęzykiem obejmuje operatorów (np->
jest słuszną asocjacyjne operator infix w tym języku), zmienne (npf
,a
ib
) oraz „Aplikacja” jednego wyrazu do drugiego typu (na przykładf a
jestf
stosowane doa
).Czy wspomniałem, jak w wielu językach pomocne było przypisywanie typów do wyrażeń programu w celu opisania klas wartości wyrażeń? Cóż, w tym podrzędnym języku typów wyrażenia oceniają na typy (a nie na wartości ), dlatego pomocne jest przypisywanie rodzajów do wyrażeń typu w celu opisania klas typów , które mogą reprezentować. Zasadniczo posiadanie rodzajów pomaga nam pisać wyrażenia typów, które będą działać poprawnie w szerokim zakresie typów .
Tak więc wartości są typu jako typy są do rodzajów i typów pomóc nam pisać wartość programów -level natomiast rodzaje pomóc nam pisać typu programów -level.
Jak wyglądają te rodzaje ? Rozważmy podpis typu:
Jeśli wyrażenie typu
a -> a
jest ważne, co niby od rodzaju powinniśmy pozwolić zmiennaa
być? Cóż, wyrażenia typu:wyglądać prawidłowy, więc typów
Int
iBool
są oczywiście z prawej rodzaju . Ale nawet bardziej skomplikowane typy, takie jak:wyglądać poprawnie. W rzeczywistości, ponieważ powinniśmy być w stanie wywoływać
id
funkcje, nawet:wygląda w porządku. Więc
Int
,Bool
,[Double]
,Maybe [(Double,Int)]
, aa -> a
wszystkie wyglądają jak typy z prawej rodzaju .Innymi słowy, wygląda na to, że istnieje tylko jeden rodzaj , nazwijmy go
*
jak wieloznaczny uniks, a każdy typ ma ten sam rodzaj*
, koniec historii.Dobrze?
Cóż, niezupełnie. Okazuje się, że
Maybe
samo w sobie jest tak samo poprawnym wyrażeniem typu jakMaybe Int
(w bardzo podobny sposóbsqrt
, samo w sobie, jest tak samo poprawnym wyrażeniem wartości jaksqrt 25
). Jednak następujące wyrażenie typu jest nieprawidłowe:Ponieważ, gdy
Maybe
jest wyrażeniem typu, nie stanowią swego rodzaju z rodzaju , które mogą mieć wartości. Tak, to w jaki sposób należy zdefiniować*
- to rodzaj z typów , które mają wartości; zawiera „Complete” typy jakDouble
iMaybe [(Double,Int)]
ale nie obejmuje niekompletne, typy bezwartościowe podobaEither String
. Dla uproszczenia nazywam te kompletne rodzaje*
„konkretnymi typami”, chociaż terminologia ta nie jest uniwersalna, a „konkretne typy” mogą oznaczać coś zupełnie innego niż, powiedzmy, programista C ++.Teraz w wyrażeniu typu
a -> a
, o ile typa
ma rodzaj*
(rodzaj konkretnych typów), wynik wyrażenia typu równieża -> a
będzie miał rodzaj (tj. Rodzaj konkretnych typów).*
Więc co niby od typu jest
Maybe
? Cóż,Maybe
można go zastosować do rodzaju betonu, aby uzyskać inny rodzaj betonu. Tak,Maybe
wygląda jak mały jak rodzaj funkcji poziomu, że trwa typu o rodzaju*
i zwraca typ z rodzaju*
. Gdybyśmy mieli funkcję poziomu wartość, która odbyła się wartość od rodzajuInt
i zwracana jest wartość o rodzajuInt
, że damy mu rodzaj podpisuInt -> Int
, więc przez analogię powinniśmy daćMaybe
się rodzaj podpisu* -> *
. GHCi zgadza się:Wracam do:
W podpisie tego typu zmienna
f
ma rodzaj* -> *
i zmiennea
orazb
ma rodzaj*
; wbudowany operator->
ma rodzaj* -> * -> *
(pobiera rodzaj*
po lewej i jeden po prawej i zwraca również rodzaj*
). Na podstawie tego i zasad wnioskowania rodzaju można wywnioskować, żea -> b
jest to poprawny typ z rodzajem*
,f a
af b
także są poprawne typy z rodzajem*
i(a -> b) -> f a -> f b
jest to poprawny typ rodzaju*
.Innymi słowy, kompilator może „sprawdzić rodzaj” wyrażenia typu,
(a -> b) -> f a -> f b
aby sprawdzić, czy jest poprawny dla zmiennych typu właściwego rodzaju, w ten sam sposób, w jaki „sprawdza typ”,sqrt (a**2 + b**2)
aby sprawdzić, czy jest poprawny dla zmiennych właściwego typu.Powodem używania osobnych terminów „typy” i „rodzaje” (tj. Nie mówienie o „typach typów”) jest głównie po to, aby uniknąć zamieszania. Powyższe rodzaje wyglądają bardzo inaczej niż typy i przynajmniej na początku wydają się zachowywać zupełnie inaczej. (Na przykład, zajmuje trochę czasu, aby objąć głowę myślą, że każdy „normalny” typ ma ten sam rodzaj,
*
a taki niea -> b
jest .)*
* -> *
Niektóre z nich są również historyczne. W miarę ewolucji GHC Haskell różnice między wartościami, typami i rodzajami zaczęły się zacierać. W dzisiejszych czasach wartości można „promować” na typy, a typy i rodzaje są naprawdę takie same. Tak więc we współczesnym Haskell zarówno wartości mają typy, jak i typy ARE (prawie), a rodzaje typów to po prostu więcej typów.
@ user21820 poprosił o dodatkowe wyjaśnienie „typy i rodzaje są naprawdę takie same”. Żeby było trochę jaśniej, we współczesnym GHC Haskell (myślę, że od wersji 8.0.1) typy i rodzaje są traktowane jednakowo w większości kodu kompilatora. Kompilator dokłada starań, aby komunikaty o błędach rozróżniały „typy” i „rodzaje”, w zależności od tego, czy narzeka odpowiednio na typ wartości, czy typ.
Ponadto, jeśli żadne rozszerzenia nie są włączone, można je łatwo rozróżnić w języku powierzchniowym. Na przykład typy (wartości) mają reprezentację w składni (np. W sygnaturach typów), ale rodzaje (typów) są - jak sądzę - całkowicie niejawne i nie ma wyraźnej składni tam, gdzie się pojawiają.
Ale jeśli włączysz odpowiednie rozszerzenia, rozróżnienie między rodzajami i rodzajami w dużej mierze zniknie. Na przykład:
Tutaj
Bar
jest (zarówno wartość, jak i) typ. Jako typ, jego rodzajem jestBool -> * -> Foo
, która jest funkcją na poziomie rodzaju , która przyjmuje rodzajBool
(który jest typem, ale także rodzaj) i rodzaj rodzaju*
i tworzy rodzajFoo
. Więc:poprawnie sprawdza rodzaj.
Jak wyjaśnia @AndrejBauer w swojej odpowiedzi, brak rozróżnienia typów i rodzajów jest niebezpieczny - posiadanie typu / rodzaju,
*
którego typ / rodzaj sam jest (co ma miejsce we współczesnym Haskell), prowadzi do paradoksów. Jednak system typów Haskell jest już pełen paradoksów z powodu braku rozwiązania, więc nie jest to uważane za coś wielkiego.źródło
type
jest po prostutype
sam i nie byłoby takiej potrzebykind
. Czym dokładnie jest to rozróżnienie?Jeśli wiesz o różnicy między zbiorami a klasami w teorii zbiorów, pomocne może być rozważenie tej sprawy jako Jeśli nie, możesz myśleć o rodzajach jako o typach „dużych” lub „wyższych”, których elementami mogą być typy lub mogą one w pewien sposób obejmować typy. Na przykład:t y p e : k i n d = s e t : c l a s s .
Bool
jest typemType
jest rodzajem, ponieważ jego elementami są typyBool -> Int
jest typemBool -> Type
jest rodzajem, ponieważ jego elementy są funkcjami zwracającymi typyBool * Int
jest typemBool * Type
jest rodzajem, ponieważ jego elementy są parami z jednym składnikiem typuW teorii typów zwykle niepożądane jest posiadanie typu wszystkich typów (prowadzi to do pardoksów). Zamiast tego możemy mieć wszechświaty , które są typami zawierającymi inne, mniejsze typy. Na przykład możemy mieć serię wszechświatów , , , ... gdzie zawiera podstawowe rzeczy, takie jak , , , podczas gdy zawiera i i itd. Ogólnie zawieraU0 U1 U2) U0 Bool Nat Nat→Nat U1 U0 Bool→U0 U0→U0 Un+1 Un jako element, a także wszystko inne, co możemy zbudować z i wszystkich rzeczy, które były przed nim, używając podstawowych operacji , itp.Un × →
Wyjaśniam to wszystko, ponieważ często wystarczy tylko i , w którym to przypadku elementy są zwykle nazywane typami, a elementy nazywane są rodzajami . W terminologii Haskell najmniejszy wszechświat jest zapisany jako . Jest to zatem element , którego Haskell nie wymienia wprost.U0 U1 U0 U 1 U 0U1 U0
*
*
U_1
źródło
Type :: Type
jest aksjomatem. W tym przypadku rozróżnienie między „typem” a „rodzajem” jest całkowicie ludzkie.True
ma typBool
iBool
ma typType
, który sam ma typType
. Czasami nazywamy typ rodzajem, aby podkreślić, że jest to typ jednostki na poziomie typu, ale w Haskell wciąż jest to tylko typ. W systemie, w którym faktycznie istnieją wszechświaty, takim jak Coq, wówczas „typ” może odnosić się do jednego wszechświata, a „miły” do innego, ale wtedy zwykle chcemy nieskończenie wielu wszechświatów.Type :: Type
i rozróżnienie między rodzajami i rodzajami. Jaki fragment kodu demonstrujeType :: Type
w Haskell?*
w Haskell jest swego rodzaju wszechświat. Po prostu tego tak nie nazywają.Type
zData.Kinds
i*
powinny być synonimami. Początkowo mieliśmy tylko*
prymityw, a obecnie jest wewnętrznie zdefiniowany jakGHC.Types.Type
w module wewnętrznymGHC.Types
, a z kolei zdefiniowany jakotype Type = TYPE LiftedRep
. Myślę, żeTYPE
to prawdziwy prymitywny, zapewniający rodzinę rodzajów (typy podnoszone, typy rozpakowane, ...). Większość „nieeleganckiej” złożoności polega tutaj na wspieraniu optymalizacji na niskim poziomie, a nie z przyczyn teoretycznych.v
jest to wartość, to ma typ:v :: T
. JeśliT
jest to rodzaj, to ma typ:T :: K
. Rodzaj typu nazywany jest jego rodzajem. Typy, które wyglądają,TYPE rep
mogą być nazywane rodzajami, chociaż słowo to jest rzadkie. IFFT :: TYPE rep
jestT
może pojawić się na RHS od a::
. Słowo „miły” ma w tym niuans:K
inT :: K
jest rodzajem, ale nie jest w nimv :: K
, chociaż jest taki samK
. Moglibyśmy zdefiniować, że „K
jest rodzajem, jeśli jest to coś w rodzaju„ aka ”, rodzaje znajdują się na RHS::
„, ale to nie przechwytuje poprawnie użycia. Dlatego moja pozycja „ludzkiego wyróżnienia”.Wartość jest jak specyficzny czerwony 2011 Ford Mustang z 19,206 mil na to, że siedzi w podjazd.
Ta konkretna wartość, nieformalnie, może mieć wiele rodzajów : jest to Mustang i jest to Ford, i jest to samochód, i jest to pojazd, pośród wielu wielu innych typów, które można wymyślić (rodzaj „rzeczy” należący do ciebie ”lub rodzaj„ rzeczy, które są czerwone ”lub…).
(W Haskell, w przybliżeniu do pierwszego rzędu (GADT łamią tę właściwość, a magia wokół literałów liczb i rozszerzenie OverloadedStrings nieco ją zaciemniają), wartości mają jeden główny typ zamiast mnóstwa nieformalnych „typów”, które można podać stang.
42
jest, dla celów tego wyjaśnieniaInt
,; w Haskell nie ma typu dla „liczb” lub „nawet liczb całkowitych” - lub raczej można by go utworzyć, ale byłby to typ rozłączny odInt
).Teraz „Mustang” może być podtypem „samochodu” - każda wartość, którą jest Mustang, jest również samochodem. Ale typ - lub, używając terminologii Haskella, rodzaj „Mustanga” nie jest „samochodem”. „Mustang” nie jest czymś, co można zaparkować na podjeździe lub wjechać. „Mustang” to rzeczownik, kategoria lub po prostu typ. Są to, nieformalnie, rodzaje „Mustanga”.
(Ponownie, Haskell rozpoznaje tylko jeden rodzaj dla każdej rzeczy na poziomie typu. Tak więc
Int
ma życzliwość*
, a żaden inny rodzaj. NieMaybe
ma życzliwości* -> *
i żadnych innych rodzajów. Ale intuicja powinna nadal utrzymywać:42
jestInt
, i możeszInt
z nią robić różne rzeczy. ., jak dodawanie i odejmowanieInt
sama nie jestInt
, nie ma takich jak liczbaInt + Int
może nieformalnie usłyszeć ludzie mówią, że.Int
toNum
, przez które one oznaczają, że istnieje instancja zNum
klasy typu dla typuInt
-To nie to samo jak powiedzenie, żeInt
ma rodzajNum
.Int
ma rodzaj „typu”, który w Haskell jest pisany*
.)Czy nie każdy nieformalny „typ” jest po prostu rzeczownikiem lub kategorią? Czy wszystkie typy mają ten sam rodzaj? Po co w ogóle mówić o rodzajach, jeśli są tak nudne?
W tym miejscu angielska analogia stanie się trochę nieprzyzwoita, ale proszę o wyrozumiałość: udawaj, że słowo „właściciel” w języku angielskim nie ma sensu w izolacji, bez opisu posiadanego przedmiotu. Udawaj, że gdyby ktoś nazwał cię „właścicielem”, nie miałoby to dla ciebie żadnego sensu; ale jeśli ktoś nazwał cię „właścicielem samochodu”, możesz zrozumieć, co mieli na myśli.
„Właściciel” nie ma tego samego rodzaju co „samochód”, ponieważ możesz mówić o samochodzie, ale nie możesz mówić o właścicielu w tej wymyślonej wersji języka angielskiego. Możesz mówić tylko o „właścicielu samochodu”. „Właściciel” tworzy coś w rodzaju „rzeczownika” tylko wtedy, gdy ma zastosowanie do czegoś, co ma już „rzeczownik”, na przykład „samochód”. Powiedzielibyśmy, że rodzajem „właściciela” jest „rzeczownik -> rzeczownik”. „Właściciel” jest jak funkcja, która bierze rzeczownik i tworzy z niego inny rzeczownik; ale to nie jest sam rzeczownik.
Pamiętaj, że „właściciel samochodu” nie jest podtypem „samochód”! To nie jest funkcja, która przyjmuje lub zwraca samochody! To po prostu zupełnie inny typ niż „samochód”. Opisuje wartości z dwoma rękami i dwiema nogami, które w pewnym momencie miały pewną ilość pieniędzy i zabrały je do salonu. Nie opisuje wartości, które mają cztery koła i malowanie. Pamiętaj też, że „właściciel samochodu” i „właściciel psa” są różnymi typami, a rzeczy, które możesz chcieć zrobić z jednym, mogą nie mieć zastosowania do drugiego.
(Podobnie, gdy mówimy, że
Maybe
ma to* -> *
w Haskell, mamy na myśli, że nonsensowne (formalnie; nieformalnie robimy to cały czas) mówienie o posiadaniu „aMaybe
”. Zamiast tego możemy miećMaybe Int
aMaybe String
, ponieważ są to rzeczy miły*
.)Tak więc cały sens mówienia o rodzajach jest taki, abyśmy mogli sformalizować nasze rozumowanie wokół słów takich jak „właściciel” i wymusić, abyśmy zawsze przyjmowali wartości typów, które zostały „w pełni skonstruowane” i nie są nonsensowne.
źródło
Zgadza się - zbadajmy, co to znaczy.
Int
lubText
są typami konkretnymi, aleMaybe a
są typem abstrakcyjnym . Nie stanie się konkretnym typem, dopóki nie zdecydujesz, jakiej konkretnej wartości chcesz dlaa
konkretnej zmiennej (lub wartości / wyrażenia / cokolwiek) npMaybe Text
.Mówimy, że
Maybe a
jest to konstruktor typów, ponieważ jest podobny do funkcji, która przyjmuje pojedynczy konkretny typ (np.Text
) I zwraca konkretny typ (Maybe Text
w tym przypadku). Ale inne typy konstruktorów mogą przyjąć jeszcze więcej „parametrów wejściowych”, zanim zwrócą konkretny typ. np.Map k v
musi wziąć dwa konkretne typy (np.Int
iText
), zanim będzie mógł zbudować konkretny typ (Map Int Text
).Tak więc konstruktory typu
Maybe a
iList a
mają tę samą „sygnaturę”, którą oznaczamy jako* -> *
(podobnie jak sygnatura funkcji Haskell), ponieważ jeśli podasz im jeden konkretny typ, wyplują konkretny typ. Nazywamy to „rodzajem” tego typuMaybe
iList
mamy ten sam rodzaj.Mówi się, że typy betonowe są miłe
*
, a nasz przykład mapy jest miły,* -> * -> *
ponieważ przyjmuje dwa typy betonu jako dane wejściowe, zanim będzie mógł wygenerować typ konkretny.Widzisz, że chodzi głównie o liczbę „parametrów”, które przekazujemy konstruktorowi typów - ale zdaj sobie sprawę, że możemy również uzyskać konstruktory typów zagnieżdżone w konstruktorach typów, dzięki czemu możemy uzyskać rodzaj, który wygląda
* -> (* -> *) -> *
na przykład .Jeśli jesteś programistą Scala / Java, to wyjaśnienie może również okazać się pomocne: https://www.atlassian.com/blog/archives/scala-types-of-a-higher-kind
źródło
Maybe a
, synonimforall a. Maybe a
, polimorficzny typu rodzaju*
iMaybe
, monomorficzny rodzaj rodzaju* -> *
.