Хаскелл типа против конструктора данных
Я учусь у Хаскелла learnyouahaskell.com. у меня возникли проблемы с пониманием конструкторов типов и конструкторов данных. Например, я действительно не понимаю разницы между этим:
data Car = Car { company :: String
, model :: String
, year :: Int
} deriving (Show)
и так:
data Car a b c = Car { company :: a
, model :: b
, year :: c
} deriving (Show)
Я понимаю, что первый просто использует один конструктор (Car
) для данных типа Car
. Я действительно не понимаю второго.
кроме того, как определяются типы данных, такие как это:
data Color = Blue | Green | Red
вписывается во все это?
из того, что я понимаю, третий пример (Color
) - Это тип, который может быть в трех состояниях: Blue
,Green
или Red
. Но это противоречит тому, как я понимаю первые два примера: это то, что тип Car
может быть только в одном государстве, Car
, который может принимать различные параметры, чтобы построить? Если да, то как вписывается второй пример?
по существу, я ищу объяснение, которое объединяет выше трех примеров кода / конструкций.
6 ответов:
на
data
объявление, a конструктор типа это вещь на левой стороне знака равенства. Элемент конструктор данных(с) это вещи на правой стороне знака равенства. Конструкторы типов используются там, где ожидается тип, а конструкторы данных-там, где ожидается значение.конструкторы данных
чтобы сделать вещи простыми, мы можем начать с примера такого типа, который представляет собой цвет.
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
у нас есть построил a значение типа
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
стала функция. Это занимает тип как его аргумент, и он возвращает новый тип.здесь важно рассмотреть разницу между a конкретного типа (примеры:
Int
,[Char]
иMaybe Bool
), который является типом, который может быть присвоено значение в вашей программе, и конструктор типа функция который вам нужно кормить тип, чтобы быть в состоянии быть присвоено значение. Значение никогда не может быть типа "list", потому что оно должно быть " list чего-то". В том же духе значение никогда не может быть типа "двоичное дерево", потому что оно должно быть "двоичным деревом хранения что-то".если мы, скажем,
Bool
в качестве аргументаBTree
, он возвращает типBTree Bool
, который является двоичным деревом, которое хранитBool
s. Заменить каждое вхождение переменной типаa
С типомBool
, и вы можете сами убедиться, насколько это правда.если вы хотите, вы можете посмотреть
BTree
как функция с видBTree :: * -> *
виды чем – то похожи на типы-the
*
указывает на конкретный тип, так скажемBTree
это от конкретного типа к конкретному типу.подводя итоги
отойти на минутку и обратите внимание на сходные элементы.
A конструктор данных это "функция", которая принимает 0 или более значения и возвращает вам новое значение.
A конструктор типа это "функция", которая принимает 0 или более типы и возвращает вам новый тип.
конструкторы данных с параметрами прохладно, если мы хотим небольшие изменения в наших значениях-мы ставим те вариации параметров и пусть парень, который создает значение, решает, какие аргументы они собираются ввести. В том же смысле конструкторы типов с параметрами хороши, если мы хотим незначительных изменений в наших типах! Мы помещаем эти вариации в качестве параметров и позволяем парню, который создает тип, решить, какие аргументы они собираются вставить.
исследование
как главная растяжка здесь, мы можем рассмотреть
Maybe a
тип. Его определение этоdata Maybe a = Nothing | Just a
здесь
Maybe
- это конструктор типов, который возвращает конкретный тип.Just
- это конструктор данных, который возвращает значение.Nothing
- это конструктор данных, содержащий значение. Если мы посмотрим на типJust
, мы видим, чтоJust :: a -> Maybe a
другими словами,
Just
принимает значение типаa
и возвращает значение типаMaybe a
. Если мы посмотрим на видMaybe
, мы видим, чтоMaybe :: * -> *
другими словами,
Maybe
принимает a конкретный тип и возвращает конкретный тип.в очередной раз! Разница между конкретным типом и функцией конструктора типа. Вы не можете создать список
Maybe
S - Если вы попытаетесь выполнить[] :: [Maybe]
вы получите сообщение об ошибке. Однако вы можете создать список
Maybe Int
илиMaybe a
. Это потому чтоMaybe
- это функция конструктор типа, но список должен содержать значения конкретного типа.Maybe Int
иMaybe a
конкретные типы (или, если хотите, вызовы функции конструктора типов, возвращающие конкретные типы.)
в Haskell алгебраические типы данных, что очень мало других языков. Возможно, это и сбивает вас с толку.
на других языках вы обычно можете сделать "запись", "структуру" или аналогичную, которая имеет кучу именованных полей, которые содержат различные типы данных. Вы также можете иногда сделать "перечисление", которое имеет (небольшой) набор фиксированных значений (например,
Red
,Green
иBlue
).в Haskell, вы можете комбината оба они одновременно. Странно, но это правда!
почему это называется "алгебраическая"? Ну, ботаники говорят о" типах суммы "и"типах продукта". Например:
data Eg1 = One Int | Two String
An
Eg1
стоимость в основном или целое число или строка. Так что набор всех возможныхEg1
values - это "сумма" набора всех возможных целочисленных значений и всех возможных строковых значений. Таким образом, ботаники относятся кEg1
как "тип суммы". С другой рука:data Eg2 = Pair Int String
Eg2
значение состоит из и целое число и строку. Так что набор всех возможныхEg2
values-декартово произведение множества всех целых чисел и множества всех строк. Два набора " умножаются "вместе, так что это"тип продукта".алгебраические типы Хаскелла сумма типов типов продуктов. Вы даете конструктору несколько полей для создания типа продукта, и у вас есть несколько конструкторы, чтобы сделать сумму (продуктов).
в качестве примера того, почему это может быть полезно, предположим, что у вас есть что - то, что выводит данные как XML или JSON, и он принимает запись конфигурации-но, очевидно, параметры конфигурации для XML и для JSON совершенно разные. Так ты может сделать что-то вроде этого:
data Config = XML_Config {...} | JSON_Config {...}
(С некоторыми подходящими полями там, очевидно.) Вы не можете делать такие вещи на обычных языках программирования, которые почему большинство людей не привыкли к ним.
Начну с самого простого случая:
data Color = Blue | Green | Red
это определяет "конструктор типа"
Color
который не принимает аргументов - и он имеет три "конструктора данных",Blue
,Green
иRed
. Ни один из конструкторов данных не принимает никаких аргументов. Это означает, что есть три типаColor
:Blue
,Green
иRed
.конструктор данных используется, когда вам нужно создать какое-то значение. Например:
myFavoriteColor :: Color myFavoriteColor = Green
создает значение
myFavoriteColor
С помощьюGreen
конструктор данных - иmyFavoriteColor
будет типаColor
так как это тип значений, создаваемых конструктором данных.конструктор типа используется, когда вам нужно создать тип какой-то. Обычно это происходит при написании подписи:
isFavoriteColor :: Color -> Bool
в этом случае, называя
Color
тип конструктора (который не принимает аргументов).все еще со мной?
теперь представьте вы не только хотели создать красные/зеленые/синие значения, но вы также хотели указать "интенсивность". Например, значение от 0 до 256. Вы можете сделать это, добавив аргумент к каждому из конструкторов данных, так что вы получите:
data Color = Blue Int | Green Int | Red Int
Итак, каждый из трех конструкторов данных принимает аргумент типа
Int
. Конструктор типа (Color
) по-прежнему не принимает никаких аргументов. Итак, мой любимый цвет-темно-зеленый, я мог бы написатьmyFavoriteColor :: Color myFavoriteColor = Green 50
и снова, он называет
Green
конструктор данных, и я получаю значение типаColor
.представьте себе, если вы не хотите, чтобы диктовать, как люди выражают интенсивность цвета. Некоторые, возможно, числовое значение, как мы только что сделали. Другие могут быть прекрасны только с булевым указанием "яркий"или" не очень яркий". Решение этой проблемы заключается в том, чтобы не жестко
Int
в конструкторах данных, но вместо этого используйте переменную типа:data Color a = Blue a | Green a | Red a
теперь наш конструктор типов принимает один аргумент (другой тип, который мы называем
a
!) и все конструкторы данных будут принимать один аргумент (значение!) такого типаa
. Так что вы могли быmyFavoriteColor :: Color Bool myFavoriteColor = Green False
или
myFavoriteColor :: Color Int myFavoriteColor = Green 50
обратите внимание, как мы называем
Color
конструктор типа с аргументом (другой тип), чтобы получить "эффективный" тип, который будет возвращен конструкторами данных. Это касается концепции видов что вы можете прочитать О за чашкой кофе или два.теперь мы выяснили, что такое конструкторы данных и конструкторы типов, и как конструкторы данных могут принимать другие значения в качестве аргументов, а конструкторы типов могут принимать другие типы в качестве аргументов. ХТ.
как указывали другие, полиморфизм здесь не так уж и полезен. Давайте посмотрим на другой пример, с которым вы, вероятно, уже знакомы:
Maybe a = Just a | Nothing
этот тип имеет два конструктора данных.
Nothing
несколько скучно, он не содержит никаких полезных данных. С другой стороныJust
содержит значениеa
- любой типa
возможно. Давайте напишем функцию, которая использует этот тип, например, получая головуInt
список, если есть (я надеюсь, что вы согласны с этим полезнее, чем выбрасывать ошибку):maybeHead :: [Int] -> Maybe Int maybeHead [] = Nothing maybeHead (x:_) = Just x > maybeHead [1,2,3] -- Just 1 > maybeHead [] -- None
так что в данном случае
a
этоInt
, но это будет работать также и для любого другого типа. Фактически вы можете заставить нашу функцию работать для каждого типа списка (даже без изменения реализации):maybeHead :: [t] -> Maybe t maybeHead [] = Nothing maybeHead (x:_) = Just x
С другой стороны вы можете писать функции, которые принимают только определенный тип
Maybe
, например,doubleMaybe :: Maybe Int -> Maybe Int doubleMaybe Just x = Just (2*x) doubleMaybe Nothing= Nothing
короче говоря, с полиморфизмом вы даете своему собственному типу гибкость работа со значениями других типов.
в вашем примере, вы можете решить в какой-то момент, что
String
недостаточно для идентификации компании, но она должна иметь свой собственный типCompany
(который содержит дополнительные данные, такие как страна, адрес, тайных счетов и т. д.). Ваша первая реализацияCar
нужно будет изменить, чтобы использоватьCompany
вместоString
для его первого значения. Ваша вторая реализация просто прекрасна, вы используете ее какCar Company String Int
и это будет работать, как и раньше (of функции курса доступ к данным компании должны быть изменены).
во втором есть понятие "полиморфизм".
The
a b c
может быть любого типа. Например,a
может быть[String]
,b
может быть[Int]
иc
может быть[Char]
.в то время как первый тип фиксируется: компания является
String
, модельString
год составляетInt
.пример автомобиля может не показать значение использования полиморфизма. Но представьте, что ваши данные относятся к типу списка. Список может содержать
String, Char, Int ...
В таких ситуациях вам понадобится второй способ определения ваших данных.что касается третьего способа, я не думаю, что он должен вписываться в предыдущий тип. Это просто еще один способ определения данных в Haskell.
это мое скромное мнение, как Новичок себя.
кстати: убедитесь, что вы хорошо тренируете свой мозг и чувствуете себя комфортно. Это ключ к пониманию монады позже.
речь идет о типы: в первом случае, ваш набор типов
String
(для компании и модели) иInt
за год. Во втором случае, ваши более общие.a
,b
иc
могут быть те же самые типы, что и в первом примере, или что-то совсем другое. Например, может быть полезно дать год как строку вместо целого числа. И если вы хотите, вы можете даже использовать свойColor
тип.