В 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
, применив конструктор данных. Конструктор данных либо содержит значение, как переменная, либо принимает другие значения в качестве аргумента и создает новое значение . Если вы уже занимались программированием ранее, эта концепция не должна показаться вам странной.
антракт
Если вы хотите построить двоичное дерево для хранения String
s, вы можете представить, что делаете что-то вроде
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
, который представляет собой двоичное дерево, в котором хранится 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
конструктор данных принимает три аргумента (но типы этих аргументов в одном случае фиксированы, а в другом параметризованы).