Хаскелл типа против конструктора данных
Я учусь у Хаскелла 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путем применения конструктора данных. Конструктор данных либо содержит значение, подобное переменной, либо принимает другие значения в качестве аргумента и создает новый стоимостью. Если вы уже делали предыдущее Программирование, эта концепция не должна быть очень странной для вас.антракт
если вы хотите построить двоичное дерево для хранения
Strings, вы могли бы представить себе делать что-то вродеdata SBTree = Leaf String | Branch String SBTree SBTreeчто мы видим здесь это типа
SBTree, который содержит два конструктора данных. Иными словами, есть две функции (а именноLeafиBranch), который будет строить значенияSBTreeтип. Если вы не знакомы с тем, как работают бинарные деревья, просто держитесь там. На самом деле вам не нужно знать, как работают бинарные деревья, только то, что этот хранитStrings в некотором роде.мы также видим, что оба конструктора данных взять
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, который является двоичным деревом, которое хранитBools. Заменить каждое вхождение переменной типа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 конкретный тип и возвращает конкретный тип.в очередной раз! Разница между конкретным типом и функцией конструктора типа. Вы не можете создать список
MaybeS - Если вы попытаетесь выполнить[] :: [Maybe]вы получите сообщение об ошибке. Однако вы можете создать список
Maybe IntилиMaybe a. Это потому чтоMaybe- это функция конструктор типа, но список должен содержать значения конкретного типа.Maybe IntиMaybe aконкретные типы (или, если хотите, вызовы функции конструктора типов, возвращающие конкретные типы.)
в Haskell алгебраические типы данных, что очень мало других языков. Возможно, это и сбивает вас с толку.
на других языках вы обычно можете сделать "запись", "структуру" или аналогичную, которая имеет кучу именованных полей, которые содержат различные типы данных. Вы также можете иногда сделать "перечисление", которое имеет (небольшой) набор фиксированных значений (например,
Red,GreenиBlue).в Haskell, вы можете комбината оба они одновременно. Странно, но это правда!
почему это называется "алгебраическая"? Ну, ботаники говорят о" типах суммы "и"типах продукта". Например:
data Eg1 = One Int | Two StringAn
Eg1стоимость в основном или целое число или строка. Так что набор всех возможныхEg1values - это "сумма" набора всех возможных целочисленных значений и всех возможных строковых значений. Таким образом, ботаники относятся кEg1как "тип суммы". С другой рука:data Eg2 = Pair Int StringEg2значение состоит из и целое число и строку. Так что набор всех возможныхEg2values-декартово произведение множества всех целых чисел и множества всех строк. Два набора " умножаются "вместе, так что это"тип продукта".алгебраические типы Хаскелла сумма типов типов продуктов. Вы даете конструктору несколько полей для создания типа продукта, и у вас есть несколько конструкторы, чтобы сделать сумму (продуктов).
в качестве примера того, почему это может быть полезно, предположим, что у вас есть что - то, что выводит данные как 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тип.