W datadeklaracji 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.
data Colour = Red | Green | Blue
Tutaj mamy trzy konstruktory danych. Colourjest typem i Greenjest konstruktorem zawierającym wartość typu Colour. Podobnie Redi Blueoba są konstruktorami, które konstruują wartości typu Colour. Moglibyśmy sobie jednak wyobrazić doprawienie tego!
data Colour = RGB Int Int Int
Nadal mamy tylko typ Colour, ale RGBnie jest to wartość - to funkcja pobierająca trzy Inty i zwracająca wartość! RGBma typ
RGB :: Int -> Int -> Int -> Colour
RGBjest 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ę RGBdo trzech wartości, otrzymamy wartość koloru!
Prelude> RGB 12 92 27
#0c5c1b
Mamy skonstruowana wartość typu Colourpoprzez 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 Strings, możesz sobie wyobrazić coś takiego
data SBTree = Leaf String
| Branch String SBTree SBTree
Widzimy tutaj typ, SBTreektóry zawiera dwa konstruktory danych. Innymi słowy, istnieją dwie funkcje (mianowicie Leafi Branch), które konstruują wartości SBTreetypu. 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 to Stringw jakiś sposób je przechowuje .
Widzimy również, że oba konstruktory danych przyjmują Stringargument - 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:
data BBTree = Leaf Bool
| Branch Bool BBTree BBTree
Konstruktory typów
Oba SBTreei BBTreesą 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ć:
data BTree a = Leaf a
| Branch a (BTree a) (BTree a)
Teraz wprowadzamy zmienną typu a jako parametr do konstruktora typu. W tej deklaracji BTreestał się funkcją. Przyjmuje typ jako argument i zwraca nowy typ .
Ważne jest tutaj, aby wziąć pod uwagę różnicę między rodzaju betonu (przykłady obejmują Int, [Char]i Maybe Bool), który jest typem, który może być przypisany do wartości w programie, a także funkcja typu konstruktor które trzeba nakarmić typ, aby móc być przypisane do wartości. Wartość nigdy nie może być typu „lista”, ponieważ musi to być „lista czegoś ”. W tym samym duchu, wartość nigdy nie może być typu „drzewo binarne”, ponieważ musi to być „drzewo binarne przechowujące coś ”.
Jeśli przekażemy, powiedzmy, Boolargument do BTree, zwraca typ BTree Bool, który jest drzewem binarnym, które przechowuje Bools. Zastąp każde wystąpienie zmiennej atypu typem Bool, a sam przekonasz się, czy to prawda.
Jeśli chcesz, możesz wyświetlić BTreejako funkcję z rodzajem
BTree :: * -> *
Rodzaje są w pewnym sensie podobne do typów - *oznacza konkretny typ, więc mówimy, że BTreeprzechodzi 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 atyp. Jego definicja to
data Maybe a = Nothing
| Just a
Tutaj Maybejest konstruktor typu, który zwraca konkretny typ. Justjest konstruktorem danych, który zwraca wartość. Nothingjest konstruktorem danych zawierającym wartość. Jeśli spojrzymy na typ Just, widzimy to
Just :: a -> Maybe a
Innymi słowy, Justprzyjmuje wartość typu ai zwraca wartość typu Maybe a. Jeśli spojrzymy na ten rodzaj Maybe, zobaczymy to
Maybe :: * -> *
Innymi słowy, Maybeprzyjmuje konkretny typ i zwraca konkretny typ.
Jeszcze raz! Różnica między konkretnym typem a funkcją konstruktora typu. Nie możesz utworzyć listy Maybes - jeśli próbujesz wykonać
[] :: [Maybe]
pojawi się błąd. Możesz jednak utworzyć listę Maybe Intlub Maybe a. Dzieje się tak, ponieważ Maybejest to funkcja konstruktora typu, ale lista musi zawierać wartości konkretnego typu. Maybe Inti Maybe asą typami konkretnymi (lub, jeśli chcesz, wywołania funkcji konstruktora typu, które zwracają konkretne typy).
Carjest zarówno konstruktorem typu (po lewej stronie=), jak i konstruktorem danych (po prawej stronie). W pierwszym przykładzieCarkonstruktor typu nie przyjmuje żadnych argumentów, w drugim przyjmuje trzy. W obu przykładachCarkonstruktor danych przyjmuje trzy argumenty (ale typy tych argumentów są w jednym przypadku ustalone, aw drugim sparametryzowane).