Uczę się Haskella z learnyouahaskell.com . Mam problem ze zrozumieniem konstruktorów typów i konstruktorów danych. Na przykład tak naprawdę nie rozumiem różnicy między tym:
data Car = Car { company :: String
, model :: String
, year :: Int
} deriving (Show)
i to:
data Car a b c = Car { company :: a
, model :: b
, year :: c
} deriving (Show)
Rozumiem, że pierwszym jest po prostu użycie jednego konstruktora ( Car
) do zbudowania danych typu Car
. Drugiego naprawdę nie rozumiem.
Ponadto, w jaki sposób typy danych zdefiniowane w ten sposób:
data Color = Blue | Green | Red
pasuje do tego wszystkiego?
Z tego co rozumiem, trzeci przykład ( Color
) jest typem, który może występować w trzech stanach: Blue
, Green
lub Red
. Ale to jest sprzeczne z tym, jak rozumiem pierwsze dwa przykłady: czy jest tak, że typ Car
może być tylko w jednym stanie Car
, którego budowa może wymagać różnych parametrów? Jeśli tak, to jak pasuje do tego drugi przykład?
Zasadniczo szukam wyjaśnienia, które ujednolica powyższe trzy przykłady / konstrukcje kodu.
Car
jest zarówno konstruktorem typu (po lewej stronie=
), jak i konstruktorem danych (po prawej stronie). W pierwszym przykładzieCar
konstruktor typu nie przyjmuje żadnych argumentów, w drugim przyjmuje trzy. W obu przykładachCar
konstruktor danych przyjmuje trzy argumenty (ale typy tych argumentów są w jednym przypadku ustalone, aw drugim sparametryzowane).Car :: String -> String -> Int -> Car
) do zbudowania danych typuCar
. druga polega po prostu na użyciu jednego konstruktora danych (Car :: a -> b -> c -> Car a b c
) do tworzenia danych typuCar a b c
.Odpowiedzi:
W
data
deklaracji konstruktor typu znajduje się po lewej stronie znaku równości. Konstruktor danych (s) są rzeczy, na prawej stronie znaku równości. Używasz konstruktorów typów, w których oczekiwany jest typ, i używasz konstruktorów danych, w których oczekiwana jest wartość.Konstruktory danych
Aby uprościć sprawę, możemy zacząć od przykładu typu, który reprezentuje kolor.
Tutaj mamy trzy konstruktory danych.
Colour
jest typem iGreen
jest konstruktorem zawierającym wartość typuColour
. PodobnieRed
iBlue
oba są konstruktorami, które konstruują wartości typuColour
. Moglibyśmy sobie jednak wyobrazić doprawienie tego!Nadal mamy tylko typ
Colour
, aleRGB
nie jest to wartość - to funkcja pobierająca trzy Inty i zwracająca wartość!RGB
ma typRGB
jest konstruktorem danych, który jest funkcją przyjmującą pewne wartości jako argumenty, a następnie używa ich do konstruowania nowej wartości. Jeśli zrobiłeś jakiekolwiek programowanie obiektowe, powinieneś to rozpoznać. W OOP konstruktory również przyjmują pewne wartości jako argumenty i zwracają nową wartość!W tym przypadku, jeśli zastosujemy się
RGB
do trzech wartości, otrzymamy wartość koloru!Mamy skonstruowana wartość typu
Colour
poprzez stosowanie konstruktora danych. Konstruktor danych zawiera wartość podobną do zmiennej lub przyjmuje inne wartości jako argument i tworzy nową wartość . Jeśli wcześniej programowałeś, ta koncepcja nie powinna być dla ciebie zbyt dziwna.Przerwa
Jeśli chcesz zbudować drzewo binarne do przechowywania
String
s, możesz sobie wyobrazić coś takiegoWidzimy tutaj typ,
SBTree
który zawiera dwa konstruktory danych. Innymi słowy, istnieją dwie funkcje (mianowicieLeaf
iBranch
), które konstruują wartościSBTree
typu. Jeśli nie wiesz, jak działają drzewa binarne, po prostu trzymaj się tam. Właściwie nie musisz wiedzieć, jak działają drzewa binarne, tylko, że toString
w jakiś sposób je przechowuje .Widzimy również, że oba konstruktory danych przyjmują
String
argument - jest to String, który zamierzają przechowywać w drzewie.Ale! A gdybyśmy chcieli mieć możliwość przechowywania
Bool
, musielibyśmy stworzyć nowe drzewo binarne. Może to wyglądać mniej więcej tak:Konstruktory typów
Oba
SBTree
iBBTree
są konstruktorami typów. Ale jest rażący problem. Czy widzisz, jakie są podobne? To znak, że naprawdę potrzebujesz gdzieś parametru.Więc możemy to zrobić:
Teraz wprowadzamy zmienną typu
a
jako parametr do konstruktora typu. W tej deklaracjiBTree
stał się funkcją. Przyjmuje typ jako argument i zwraca nowy typ .Jeśli przekażemy, powiedzmy,
Bool
argument doBTree
, zwraca typBTree Bool
, który jest drzewem binarnym, które przechowujeBool
s. Zastąp każde wystąpienie zmienneja
typu typemBool
, a sam przekonasz się, czy to prawda.Jeśli chcesz, możesz wyświetlić
BTree
jako funkcję z rodzajemRodzaje są w pewnym sensie podobne do typów -
*
oznacza konkretny typ, więc mówimy, żeBTree
przechodzi od konkretnego typu do konkretnego.Podsumowując
Cofnij się na chwilę i zwróć uwagę na podobieństwa.
Konstruktor danych to „funkcja”, która przyjmuje 0 lub więcej wartości i zwraca nową wartość.
Konstruktor typów to „funkcja”, która przyjmuje 0 lub więcej typów i zwraca nowy typ.
Konstruktory danych z parametrami są fajne, jeśli zależy nam na niewielkich różnicach w naszych wartościach - umieszczamy te różnice w parametrach i pozwalamy facetowi, który tworzy wartość, decydować, jakie argumenty wstawią. W tym samym sensie konstruktory typów z parametrami są fajne jeśli chcemy drobnych różnic w naszych typach! Umieszczamy te warianty jako parametry i pozwalamy facetowi, który tworzy typ, zdecydować, jakie argumenty wstawią.
Studium przypadku
Jako odcinek domowy możemy rozważyć
Maybe a
typ. Jego definicja toTutaj
Maybe
jest konstruktor typu, który zwraca konkretny typ.Just
jest konstruktorem danych, który zwraca wartość.Nothing
jest konstruktorem danych zawierającym wartość. Jeśli spojrzymy na typJust
, widzimy toInnymi słowy,
Just
przyjmuje wartość typua
i zwraca wartość typuMaybe a
. Jeśli spojrzymy na ten rodzajMaybe
, zobaczymy toInnymi słowy,
Maybe
przyjmuje konkretny typ i zwraca konkretny typ.Jeszcze raz! Różnica między konkretnym typem a funkcją konstruktora typu. Nie możesz utworzyć listy
Maybe
s - jeśli próbujesz wykonaćpojawi się błąd. Możesz jednak utworzyć listę
Maybe Int
lubMaybe a
. Dzieje się tak, ponieważMaybe
jest to funkcja konstruktora typu, ale lista musi zawierać wartości konkretnego typu.Maybe Int
iMaybe a
są typami konkretnymi (lub, jeśli chcesz, wywołania funkcji konstruktora typu, które zwracają konkretne typy).źródło
data Colour = Red | Green | Blue
„ w ogóle nie mamy żadnych konstruktorów” jest po prostu błędne. Konstruktory typów i konstruktory danych nie muszą pobierać argumentów, patrz np. Haskell.org/haskellwiki/Constructor, który wskazuje, że w programiedata Tree a = Tip | Node a (Tree a) (Tree a)
„istnieją dwa konstruktory danych, Tip i Node”.-XEmptyDataDecls
), które pozwala to zrobić. Ponieważ, jak mówisz, nie ma wartości tego typu, funkcjaf :: Int -> Z
może na przykład nigdy nie zwracać (bo co by zwróciła?). Mogą być jednak przydatne, gdy potrzebujesz typów, ale tak naprawdę nie przejmujesz się wartościami .:k Z
i po prostu dał mi to gwiazdę.Haskell ma algebraiczne typy danych , które ma bardzo niewiele innych języków. Być może właśnie to cię dezorientuje.
W innych językach można zwykle utworzyć „rekord”, „strukturę” lub coś podobnego, które zawiera kilka nazwanych pól zawierających różne typy danych. Można również czasami zrobić „wyliczenie”, który ma (mały) zestaw możliwych wartości stałych (np twój
Red
,Green
iBlue
).W Haskell możesz łączyć oba te elementy jednocześnie. Dziwne, ale prawdziwe!
Dlaczego nazywa się to „algebraicznym”? Cóż, nerdy mówią o „typach sum” i „typach produktów”. Na przykład:
Eg1
Wartość jest w zasadzie albo liczbą całkowitą lub łańcuchem. Zatem zbiór wszystkich możliwychEg1
wartości jest „sumą” zbioru wszystkich możliwych wartości całkowitych i wszystkich możliwych wartości łańcuchowych. Dlatego nerdy określane sąEg1
jako „typ sumy”. Z drugiej strony:Każda
Eg2
wartość składa się zarówno liczbą całkowitą i ciąg. Zatem zbiór wszystkich możliwychEg2
wartości jest iloczynem kartezjańskim zbioru wszystkich liczb całkowitych i zbioru wszystkich ciągów. Te dwa zestawy są „mnożone” razem, więc jest to „typ produktu”.Typy algebraiczne Haskella to sumy typów produktów . Dajesz konstruktorowi wiele pól, aby utworzyć typ produktu, i masz wiele konstruktorów, aby utworzyć sumę (produktów).
Jako przykład, dlaczego może to być przydatne, załóżmy, że masz coś, co wyprowadza dane jako XML lub JSON i wymaga rekordu konfiguracji - ale oczywiście ustawienia konfiguracji dla XML i JSON są zupełnie inne. Więc może zrobić coś takiego:
(Oczywiście z odpowiednimi polami). Nie można robić takich rzeczy w normalnych językach programowania, dlatego większość ludzi nie jest do tego przyzwyczajona.
źródło
union
s, z dyscypliną tagów. :)union
, ludzie patrzą na mnie jak „kto do diabła kiedykolwiek tego używa ?” ;-)union
używanych w mojej karierze C. Proszę, nie mów, że to niepotrzebne, ponieważ tak nie jest.Zacznij od najprostszego przypadku:
Definiuje „konstruktor typu”,
Color
który nie przyjmuje argumentów - i ma trzy „konstruktory danych”Blue
,Green
iRed
. Żaden z konstruktorów danych nie przyjmuje żadnych argumentów. Oznacza to, że istnieją trzy typuColor
:Blue
,Green
iRed
.Konstruktor danych jest używany, gdy trzeba utworzyć jakąś wartość. Lubić:
tworzy wartość
myFavoriteColor
przy użyciuGreen
konstruktora danych - imyFavoriteColor
będzie typu,Color
ponieważ jest to typ wartości generowanych przez konstruktor danych.Konstruktor typów jest używany, gdy trzeba utworzyć jakiś typ . Zwykle ma to miejsce podczas pisania podpisów:
W tym przypadku wywołujesz
Color
konstruktor typu (który nie przyjmuje żadnych argumentów).Nadal ze mną?
Teraz wyobraź sobie, że nie tylko chciałeś stworzyć wartości koloru czerwonego / zielonego / niebieskiego, ale także chciałeś określić „intensywność”. Na przykład wartość z przedziału od 0 do 256. Możesz to zrobić, dodając argument do każdego z konstruktorów danych, więc otrzymasz:
Teraz każdy z trzech konstruktorów danych przyjmuje argument typu
Int
. Konstruktor typu (Color
) nadal nie przyjmuje żadnych argumentów. Więc mój ulubiony kolor to ciemnozielony, mogłem pisaćI znowu wywołuje
Green
konstruktor danych i otrzymuję wartość typuColor
.Wyobraź sobie, że nie chcesz dyktować ludziom sposobu wyrażania intensywności koloru. Niektórzy mogą chcieć wartości liczbowej, tak jak właśnie zrobiliśmy. Inni mogą być w porządku, jeśli tylko wartość logiczna oznacza „jasny” lub „nie tak jasny”. Rozwiązaniem tego problemu jest nie kodowanie
Int
na stałe w konstruktorach danych, ale raczej użycie zmiennej typu:Teraz nasz konstruktor typu przyjmuje jeden argument (inny typ, który właśnie wywołujemy
a
!), A wszystkie konstruktory danych przyjmą jeden argument (wartość!) Tego typua
. Więc mogłeślub
Zwróć uwagę, jak wywołujemy
Color
konstruktor typu z argumentem (innym typem), aby uzyskać typ „efektywny”, który zostanie zwrócony przez konstruktory danych. Dotyka to pojęcia rodzajów, o których warto poczytać przy filiżance kawy lub dwóch.Teraz dowiedzieliśmy się, jakie są konstruktory danych i konstruktory typów oraz w jaki sposób konstruktory danych mogą przyjmować inne wartości jako argumenty, a konstruktory typów mogą przyjmować inne typy jako argumenty. HTH.
źródło
->
w podpisie.a
indata Color a = Red a
.a
jest symbolem zastępczym dla dowolnego typu. Możesz mieć to samo w zwykłych funkcjach, np. Funkcja typu(a, b) -> a
pobiera krotkę dwóch wartości (typówa
ib
) i zwraca pierwszą wartość. Jest to funkcja „ogólna”, ponieważ nie narzuca typu elementów krotki - określa jedynie, że funkcja zwraca wartość tego samego typu, co pierwszy element krotki.Now, our type constructor takes one argument (another type which we just call a!) and all of the data constructors will take one argument (a value!) of that type a.
Jest to bardzo pomocne.Jak zauważyli inni, polimorfizm nie jest tutaj tak strasznie przydatny. Spójrzmy na inny przykład, który prawdopodobnie już znasz:
Ten typ ma dwa konstruktory danych.
Nothing
jest trochę nudny, nie zawiera żadnych przydatnych danych. Z drugiej stronyJust
zawiera wartośća
- dowolnego typua
. Napiszmy funkcję używającą tego typu, np. Pobieranie nagłówkaInt
listy, jeśli taka istnieje (mam nadzieję, że się zgadzasz, jest to bardziej przydatne niż rzucanie błędu):Więc w tym przypadku
a
jest toInt
, ale zadziałaby również dla każdego innego typu. W rzeczywistości możesz sprawić, by nasza funkcja działała dla każdego typu listy (nawet bez zmiany implementacji):Z drugiej strony możesz pisać funkcje, które akceptują tylko określony typ
Maybe
, npKrótko mówiąc, dzięki polimorfizmowi dajesz swojemu typowi elastyczność w pracy z wartościami innych typów.
W swoim przykładzie możesz w pewnym momencie zdecydować, że
String
nie wystarczy do zidentyfikowania firmy, ale musi ona mieć swój własny typCompany
(który zawiera dodatkowe dane, takie jak kraj, adres, rachunki tylne itp.). Twoja pierwsza implementacjaCar
musiałaby zostać zmieniona na używanieCompany
zamiastString
pierwszej wartości. Twoja druga implementacja jest w porządku, używasz jej takCar Company String Int
, jak poprzednio (oczywiście funkcje dostępu do danych firmy wymagają zmiany).źródło
data Color = Blue ; data Bright = Color
? Wypróbowałem to w ghci i wydaje się, że konstruktor Color w typie nie ma nic wspólnego z konstruktorem danych Color w definicji Bright. Istnieją tylko 2 konstruktory Color, jeden to Data, a drugi to Type.data
lubnewtype
(np.data Bright = Bright Color
), Lub możesz użyć gotype
w celu zdefiniowania synonimu (nptype Bright = Color
.).Drugi zawiera w sobie pojęcie „polimorfizmu”.
a b c
Może być dowolnego typu. Na przykłada
może być[String]
,b
może być[Int]
ic
może być[Char]
.Podczas gdy pierwszy typ jest ustalony: firma to a
String
, model to a,String
a rok toInt
.Przykład samochodu może nie wskazywać na znaczenie stosowania polimorfizmu. Ale wyobraź sobie, że twoje dane są typu listy. Lista może zawierać.
String, Char, Int ...
W takich sytuacjach potrzebny będzie drugi sposób zdefiniowania danych.Jeśli chodzi o trzeci sposób, nie sądzę, aby pasował do poprzedniego typu. To tylko inny sposób definiowania danych w Haskell.
To jest moja skromna opinia jako początkującego.
Przy okazji: upewnij się, że dobrze trenujesz swój mózg i czujesz się z tym dobrze. To klucz do późniejszego zrozumienia Monady.
źródło
Chodzi o typy : w pierwszym przypadku ustawiasz typy
String
(dla firmy i modelu) orazInt
rok. W drugim przypadku twój jest bardziej ogólny.a
,b
ic
mogą być tego samego typu, co w pierwszym przykładzie, lub coś zupełnie innego. Np. Przydatne może być podanie roku w postaci łańcucha zamiast liczby całkowitej. A jeśli chcesz, możesz nawet użyć swojegoColor
typu.źródło