У data
декларації конструктором типу є річ з лівої сторони знака рівності. Конструктор (s) дані речі на правій стороні від знака рівності. Ви використовуєте конструктори типів там, де очікується тип, і ви використовуєте конструктори даних там, де очікується значення.
Конструктори даних
Щоб зробити прості речі, ми можемо почати з прикладу типу, який представляє колір.
data Colour = Red | Green | Blue
Тут ми маємо три конструктори даних. Colour
є типом і Green
є конструктором, який містить значення типу Colour
. Аналогічно Red
і Blue
обидва конструктори, які будують значення типу Colour
. Ми могли б уявити, як це спекулює!
data Colour = RGB Int Int Int
У нас ще є лише тип Colour
, але RGB
це не значення - це функція, яка бере три Інти і повертає значення! RGB
має тип
RGB :: Int -> Int -> Int -> Colour
RGB
- це конструктор даних, який є функцією, яка приймає деякі значення в якості аргументів, а потім використовує ці для побудови нового значення. Якщо ви виконали будь-яке об'єктно-орієнтоване програмування, вам слід визнати це. В OOP конструктори також приймають деякі значення як аргументи і повертають нове значення!
У цьому випадку, якщо застосувати RGB
до трьох значень, ми отримаємо значення кольору!
Prelude> RGB 12 92 27
#0c5c1b
Ми побудували значення типу Colour
, застосувавши конструктор даних. Конструктор даних або містить значення, подібне до змінної, або приймає інші значення в якості аргументу і створює нове значення . Якщо ви раніше займалися програмуванням, ця концепція вам не повинна бути дуже дивною.
Антракт
Якщо ви хочете побудувати бінарне дерево для зберігання String
s, ви можете уявити собі щось подібне
data SBTree = Leaf String
| Branch String SBTree SBTree
Тут ми бачимо тип, SBTree
який містить два конструктори даних. Іншими словами, є дві функції (а саме Leaf
і Branch
), які будують значення SBTree
типу. Якщо ви не знайомі з тим, як працюють двійкові дерева, просто повісьте там. Вам насправді не потрібно знати, як працюють бінарні дерева, лише те, що це зберігає String
певний шлях.
Ми також бачимо, що обидва конструктори даних беруть String
аргументи - це рядок, який вони збираються зберігати у дереві.
Але! Що, якби ми також хотіли вміти зберігати Bool
, нам довелося б створити нове бінарне дерево. Це могло виглядати приблизно так:
data BBTree = Leaf Bool
| Branch Bool BBTree BBTree
Тип конструкторів
І те, SBTree
і BBTree
конструктори типу. Але є яскрава проблема. Ви бачите, наскільки вони схожі? Це знак того, що ви дійсно хочете десь параметр.
Тож ми можемо це зробити:
data BTree a = Leaf a
| Branch a (BTree a) (BTree a)
Тепер ми вводимо змінну типу a
як параметр конструктору типів. У цій декларації BTree
стало функцією. Він бере свій тип як аргумент і повертає новий тип .
Тут важливо враховувати різницю між типом бетону (приклади включають в себе Int
, [Char]
а Maybe Bool
) , який є типом , який може бути віднесений до значення в вашій програмі, а функція конструктора типу , який вам потрібно годувати тип , щоб мати можливість бути присвоєне значення. Значення ніколи не може бути типу "список", оскільки воно повинно бути "списком чогось ". У тому ж дусі значення ніколи не може бути типу "бінарне дерево", оскільки воно повинно бути "бінарним деревом, яке щось зберігає ".
Якщо ми передамо, скажімо, Bool
як аргумент BTree
, він повертає тип BTree Bool
, який є бінарним деревом, яке зберігає Bool
s. Замініть кожне виникнення змінної типу a
на тип Bool
, і ви зможете самі переконатися, як це правда.
Якщо ви хочете, ви можете розглядати BTree
як функцію з родом
BTree :: * -> *
Види дещо схожі на типи - *
вказує на конкретний тип, тому ми говоримо BTree
, що від конкретного типу до конкретного типу.
Підведенню
Відійдіть мить сюди і відзначте подібність.
Конструктор даних є «функцією» , яка приймає 0 або більше значень і дає Вам нове значення.
Конструктор типу є «функцією» , яка приймає 0 або більше типів і дає Вам новий тип.
Конструктори даних з параметрами круті, якщо ми хочемо незначних змін у наших значеннях - ми ставимо ці зміни параметрів і даємо хлопцеві, який створює значення, вирішувати, які аргументи вони збираються вносити. У цьому ж сенсі типи конструкторів з параметрами класні якщо ми хочемо невеликих варіацій у наших типах! Ми ставимо ці варіанти як параметри і даємо хлопцеві, який створює тип, вирішувати, які аргументи вони збираються викласти.
Тематичне дослідження
Оскільки тут розтягується будинок, ми можемо розглянути Maybe a
тип. Його визначення таке
data Maybe a = Nothing
| Just a
Тут Maybe
знаходиться конструктор типу, який повертає конкретний тип. Just
- конструктор даних, який повертає значення. Nothing
- конструктор даних, який містить значення. Якщо ми подивимось на тип Just
, ми це бачимо
Just :: a -> Maybe a
Іншими словами, Just
приймає значення типу a
і повертає значення типу Maybe a
. Якщо ми дивимось на вид Maybe
, ми це бачимо
Maybe :: * -> *
Іншими словами, Maybe
приймає конкретний тип і повертає конкретний тип.
Знову! Різниця між типом бетону та конструктором типу. Ви не можете створити список Maybe
s - якщо ви намагаєтеся виконати
[] :: [Maybe]
ви отримаєте помилку Однак ви можете створити список Maybe Int
, або Maybe a
. Це тому Maybe
, що це функція конструктора типів, але список повинен містити значення конкретного типу. Maybe Int
і Maybe a
це конкретні типи (або, якщо ви хочете, дзвінки вводити функції конструктора, які повертають конкретні типи.)
Car
це конструктор типу (з лівого боку=
) та конструктор даних (праворуч). У першому прикладіCar
конструктор типу не бере аргументів, у другому прикладі - три. В обох прикладахCar
конструктор даних приймає три аргументи (але типи цих аргументів в одному випадку виправлені, а в іншому параметризовані).