В dataобъявлении конструктор типа - это то, что находится слева от знака равенства. Конструкторы данных - это элементы, расположенные справа от знака равенства. Конструкторы типов используются там, где ожидается тип, и конструкторы данных, где ожидается значение.
Конструкторы данных
Чтобы упростить задачу, мы можем начать с примера шрифта, представляющего цвет.
data Colour = Red | Green | Blue
Здесь у нас есть три конструктора данных. Colour- это тип и Greenконструктор, содержащий значение типа Colour. Аналогично, Redи Blueоба являются конструкторами, которые создают значения типа Colour. Мы могли бы представить, как это приправить!
data Colour = RGB Int Int Int
У нас по-прежнему есть только тип Colour, но RGBэто не значение - это функция, которая принимает три Ints и возвращает значение! RGBимеет тип
RGB :: Int -> Int -> Int -> Colour
RGB- это конструктор данных, представляющий собой функцию, принимающую некоторые значения в качестве аргументов, а затем использующую их для создания нового значения. Если вы занимались объектно-ориентированным программированием, вы должны это признать. В ООП конструкторы также принимают некоторые значения в качестве аргументов и возвращают новое значение!
В этом случае, если мы применим RGBк трем значениям, мы получим значение цвета!
Prelude> RGB 12 92 27
#0c5c1b
Мы создали значение типа Colour, применив конструктор данных. Конструктор данных либо содержит значение, как переменная, либо принимает другие значения в качестве аргумента и создает новое значение . Если вы уже занимались программированием ранее, эта концепция не должна показаться вам странной.
антракт
Если вы хотите построить двоичное дерево для хранения Strings, вы можете представить, что делаете что-то вроде
data SBTree = Leaf String
| Branch String SBTree SBTree
Здесь мы видим тип SBTree, содержащий два конструктора данных. Другими словами, есть две функции (а именно Leafи Branch), которые будут создавать значения SBTreeтипа. Если вы не знакомы с тем, как работают бинарные деревья, просто подождите. На самом деле вам не нужно знать, как работают двоичные деревья, только то, что это Stringкаким-то образом хранит s.
Мы также видим, что оба конструктора данных принимают 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, который представляет собой двоичное дерево, в котором хранится Bools. Замените каждое вхождение переменной 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принимает конкретный тип и возвращает конкретный тип.
Снова! Разница между конкретным типом и функцией-конструктором типа. Вы не можете создать список Maybes - если вы попытаетесь выполнить
[] :: [Maybe]
вы получите сообщение об ошибке. Однако вы можете создать список Maybe Int, или Maybe a. Это потому, что Maybeэто функция конструктора типа, но список должен содержать значения конкретного типа. Maybe Intи Maybe aявляются конкретными типами (или, если хотите, вызовами функций конструктора типов, возвращающих конкретные типы).
Carэто конструктор типов (слева=) и конструктор данных (справа). В первом примереCarконструктор типа не принимает аргументов, во втором - три. В обоих примерахCarконструктор данных принимает три аргумента (но типы этих аргументов в одном случае фиксированы, а в другом параметризованы).