Я изучаю Haskell на сайте learnnyouahaskell.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
, которое может принимать различные параметры для построения? Если да, то как здесь подходит второй пример?
По сути, я ищу объяснение, которое объединяет три приведенных выше примера / конструкции кода.
Car
это конструктор типов (слева=
) и конструктор данных (справа). В первом примереCar
конструктор типа не принимает аргументов, во втором - три. В обоих примерахCar
конструктор данных принимает три аргумента (но типы этих аргументов в одном случае фиксированы, а в другом параметризованы).Car :: String -> String -> Int -> Car
) для создания данных типаCar
. второй - просто использует один конструктор данных (Car :: a -> b -> c -> Car a b c
) для создания данных типаCar a b c
.Ответы:
В
data
объявлении конструктор типа - это то, что находится слева от знака равенства. Конструкторы данных - это элементы, расположенные справа от знака равенства. Конструкторы типов используются там, где ожидается тип, и конструкторы данных, где ожидается значение.Конструкторы данных
Чтобы упростить задачу, мы можем начать с примера шрифта, представляющего цвет.
Здесь у нас есть три конструктора данных.
Colour
- это тип иGreen
конструктор, содержащий значение типаColour
. Аналогично,Red
иBlue
оба являются конструкторами, которые создают значения типаColour
. Мы могли бы представить, как это приправить!У нас по-прежнему есть только тип
Colour
, ноRGB
это не значение - это функция, которая принимает три Ints и возвращает значение!RGB
имеет типRGB
- это конструктор данных, представляющий собой функцию, принимающую некоторые значения в качестве аргументов, а затем использующую их для создания нового значения. Если вы занимались объектно-ориентированным программированием, вы должны это признать. В ООП конструкторы также принимают некоторые значения в качестве аргументов и возвращают новое значение!В этом случае, если мы применим
RGB
к трем значениям, мы получим значение цвета!Мы создали значение типа
Colour
, применив конструктор данных. Конструктор данных либо содержит значение, как переменная, либо принимает другие значения в качестве аргумента и создает новое значение . Если вы уже занимались программированием ранее, эта концепция не должна показаться вам странной.антракт
Если вы хотите построить двоичное дерево для хранения
String
s, вы можете представить, что делаете что-то вродеЗдесь мы видим тип
SBTree
, содержащий два конструктора данных. Другими словами, есть две функции (а именноLeaf
иBranch
), которые будут создавать значенияSBTree
типа. Если вы не знакомы с тем, как работают бинарные деревья, просто подождите. На самом деле вам не нужно знать, как работают двоичные деревья, только то, что этоString
каким-то образом хранит s.Мы также видим, что оба конструктора данных принимают
String
аргумент - это строка, которую они собираются сохранить в дереве.Но! Что, если бы мы также хотели иметь возможность хранить
Bool
, нам пришлось бы создать новое двоичное дерево. Это могло выглядеть примерно так:Конструкторы типов
Оба
SBTree
иBBTree
являются конструкторами типов. Но есть вопиющая проблема. Вы видите, насколько они похожи? Это признак того, что вам действительно нужен параметр.Итак, мы можем сделать это:
Теперь мы вводим переменную типа
a
в качестве параметра конструктора типа. В этом объявленииBTree
стало функцией. Он принимает тип в качестве аргумента и возвращает новый тип .Если мы передадим, скажем,
Bool
в качестве аргументаBTree
, он вернет типBTree Bool
, который представляет собой двоичное дерево, в котором хранитсяBool
s. Замените каждое вхождение переменнойa
типа типомBool
, и вы сами убедитесь, насколько это верно.Если вы хотите, вы можете просмотреть
BTree
как функцию с видомВиды чем-то похожи на типы -
*
указывает на конкретный тип, поэтому мы говорим, чтоBTree
это от конкретного типа к конкретному типу.Подведение итогов
Вернитесь сюда на мгновение и обратите внимание на сходство.
Конструктор данных является «функцией» , которая принимает 0 или больше значений и дает Вам новое значение.
Конструктор типа является «функцией» , которая принимает 0 или более типов и дает Вам новый тип.
Конструкторы данных с параметрами - это круто, если нам нужны небольшие вариации в наших значениях - мы вносим эти вариации в параметры и позволяем парню, который создает значение, решать, какие аргументы они собираются ввести. В том же смысле конструкторы типов с параметрами - это круто. если мы хотим небольшие вариации в наших типах! Мы помещаем эти вариации в качестве параметров и позволяем парню, который создает тип, решать, какие аргументы они будут вводить.
Пример из практики
В качестве финишной прямой здесь можно рассматривать
Maybe a
тип. Его определениеВот
Maybe
конструктор типа, который возвращает конкретный тип.Just
конструктор данных, возвращающий значение.Nothing
- конструктор данных, содержащий значение. Если мы посмотрим на типJust
, мы увидим, чтоДругими словами,
Just
принимает значение типаa
и возвращает значение типаMaybe a
. Если мы посмотрим на видMaybe
, мы увидим, чтоДругими словами,
Maybe
принимает конкретный тип и возвращает конкретный тип.Снова! Разница между конкретным типом и функцией-конструктором типа. Вы не можете создать список
Maybe
s - если вы попытаетесь выполнитьвы получите сообщение об ошибке. Однако вы можете создать список
Maybe Int
, илиMaybe a
. Это потому, чтоMaybe
это функция конструктора типа, но список должен содержать значения конкретного типа.Maybe Int
иMaybe a
являются конкретными типами (или, если хотите, вызовами функций конструктора типов, возвращающих конкретные типы).источник
data Colour = Red | Green | Blue
«у нас вообще нет конструкторов», совершенно неверно. Конструкторы типов и конструкторы данных не должны принимать аргументы, см., Например, haskell.org/haskellwiki/Constructor, где указано , что вdata Tree a = Tip | Node a (Tree a) (Tree a)
«есть два конструктора данных, Tip и Node».-XEmptyDataDecls
), которое позволяет вам это делать. Поскольку, как вы говорите, нет значений с этим типом, функцияf :: Int -> Z
может, например, никогда не возвращать (потому что что она вернет?) Однако они могут быть полезны, когда вам нужны типы, но на самом деле значения не важны .:k Z
и это дало мне звезду.В Haskell есть алгебраические типы данных , которых мало в других языках. Возможно, это вас смущает.
На других языках обычно можно создать «запись», «структуру» или что-то подобное, в которой есть набор именованных полей, содержащих различные типы данных. Кроме того, можно иногда сделать «перечисление», который имеет (небольшой) набор фиксированных возможных значений (например, ваш
Red
,Green
иBlue
).В Haskell вы можете комбинировать и то, и другое одновременно. Странно, но факт!
Почему это называется «алгебраическим»? Ну, ботаники говорят о «типах сумм» и «типах продуктов». Например:
Eg1
Значение в основном либо целое число или строка. Таким образом, набор всех возможныхEg1
значений - это «сумма» набора всех возможных целочисленных значений и всех возможных строковых значений. Таким образом, ботаники называютEg1
«типом суммы». С другой стороны:Каждое
Eg2
значение состоит как из целого числа, так и из строки. Таким образом, набор всех возможныхEg2
значений - это декартово произведение набора всех целых чисел и набора всех строк. Два набора «умножаются» вместе, так что это «тип продукта».Алгебраические типы Haskell - это типы сумм типов продуктов . Вы даете конструктору несколько полей для создания типа продукта, и у вас есть несколько конструкторов для создания суммы (продуктов).
В качестве примера того, почему это может быть полезно, предположим, что у вас есть что-то, что выводит данные в формате XML или JSON и принимает запись конфигурации, но, очевидно, настройки конфигурации для XML и для JSON совершенно разные. Итак, вы можете сделать что-то вроде этого:
(Очевидно, с некоторыми подходящими полями.) Вы не можете делать подобные вещи на обычных языках программирования, поэтому большинство людей к этому не привыкли.
источник
union
s, с дисциплиной тегов. :)union
, люди смотрят на меня , как «кто ад никогда не использует , что ??» ;-)union
свою карьеру на C. я видел много чего . Пожалуйста, не делайте это ненужным, потому что это не так.Начнем с самого простого случая:
Это определяет «конструктор типа»,
Color
который не принимает аргументов - и имеет три «конструктора данных»Blue
,Green
иRed
. Ни один из конструкторов данных не принимает аргументов. Это означает , что существует три типаColor
:Blue
,Green
иRed
.Конструктор данных используется, когда вам нужно создать какое-либо значение. Подобно:
создает значение
myFavoriteColor
с помощьюGreen
конструктора данных - иmyFavoriteColor
будет иметь тип,Color
поскольку это тип значений, созданных конструктором данных.Конструктор типа используется , когда нужно создать тип какой - то. Обычно это происходит при написании подписей:
В этом случае вы вызываете
Color
конструктор типа (который не принимает аргументов).Все еще со мной?
Теперь представьте, что вы не только хотите создать значения красного / зеленого / синего, но также хотите указать «интенсивность». Например, значение от 0 до 256. Вы можете сделать это, добавив аргумент к каждому из конструкторов данных, так что вы получите:
Теперь каждый из трех конструкторов данных принимает аргумент типа
Int
. Конструктор типа (Color
) по-прежнему не принимает никаких аргументов. Итак, мой любимый цвет - темно-зеленый, я мог написатьИ снова он вызывает
Green
конструктор данных, и я получаю значение типаColor
.Представьте, что вы не хотите диктовать, как люди выражают интенсивность цвета. Некоторым может потребоваться числовое значение, как мы только что сделали. Другие могут подойти только с логическим значением, указывающим «яркий» или «не такой яркий». Решение состоит в том, чтобы не жестко закодировать
Int
конструкторы данных, а использовать переменную типа:Теперь наш конструктор типа принимает один аргумент (другой тип, который мы просто вызываем
a
!), А все конструкторы данных будут принимать один аргумент (значение!) Этого типаa
. Так что вы могли быили
Обратите внимание, как мы вызываем
Color
конструктор типа с аргументом (другим типом), чтобы получить «эффективный» тип, который будет возвращен конструкторами данных. Это касается концепции видов, о которых вы, возможно, захотите прочитать за чашкой или двумя кофе.Теперь мы выяснили, что такое конструкторы данных и конструкторы типов, и как конструкторы данных могут принимать другие значения в качестве аргументов, а конструкторы типов могут принимать другие типы в качестве аргументов. НТН.
источник
->
в подписи.a
indata Color a = Red a
.a
является заполнителем для произвольного типа. Однако вы можете иметь то же самое в простых функциях, например, функция типа(a, b) -> a
принимает кортеж из двух значений (типовa
иb
) и возвращает первое значение. Это «универсальная» функция, поскольку она не определяет тип элементов кортежа - она только указывает, что функция возвращает значение того же типа, что и первый элемент кортежа.Now, our type constructor takes one argument (another type which we just call a!) and all of the data constructors will take one argument (a value!) of that type a.
Это очень полезно.Как отмечали другие, полиморфизм здесь не так уж и страшен. Давайте посмотрим на другой пример, с которым вы, вероятно, уже знакомы:
Этот тип имеет два конструктора данных.
Nothing
несколько утомляет, не содержит никаких полезных данных. С другой стороны,Just
содержит значениеa
- какой бы тип онa
ни имел. Давайте напишем функцию, которая использует этот тип, например получение заголовкаInt
списка, если он есть (надеюсь, вы согласны, что это более полезно, чем выдача ошибки):В этом случае
a
этоInt
, но он будет работать и для любого другого типа. Фактически, вы можете заставить нашу функцию работать для любого типа списков (даже без изменения реализации):С другой стороны, вы можете писать функции, которые принимают только определенный тип
Maybe
, напримерКороче говоря, с помощью полиморфизма вы даете своему собственному типу гибкость для работы со значениями разных других типов.
В вашем примере вы можете решить, что в какой-то момент этого
String
недостаточно для идентификации компании, но у нее должен быть собственный типCompany
(который содержит дополнительные данные, такие как страна, адрес, резервные счета и т. Д.). В вашей первой реализацииCar
нужно будет изменить использованиеCompany
вместоString
первого значения. Ваша вторая реализация в порядке, вы используете ее,Car Company String Int
и она будет работать как раньше (конечно, функции доступа к данным компании необходимо изменить).источник
data Color = Blue ; data Bright = Color
? Я пробовал это в ghci, и кажется, что Color в конструкторе типа не имеет ничего общего с конструктором данных Color в определении Bright. Есть всего 2 конструктора Color, один из которых - Data, а другой - Type.data
илиnewtype
(напримерdata Bright = Bright Color
), или вы можете использоватьtype
, чтобы определить синоним (напримерtype Bright = Color
).Во втором есть понятие «полиморфизм».
a b c
Может быть любого типа. Например,a
может быть[String]
,b
может быть[Int]
иc
может быть[Char]
.Пока фиксирован первый тип: компания - это
String
, модель -String
а, год -Int
.Пример Car может не показывать важность использования полиморфизма. Но представьте, что ваши данные относятся к типу списка. Список может содержать
String, Char, Int ...
В таких ситуациях вам понадобится второй способ определения ваших данных.Что касается третьего способа, я не думаю, что он должен вписываться в предыдущий тип. Это всего лишь еще один способ определения данных в Haskell.
Это мое скромное мнение как новичка.
Кстати: убедитесь, что вы хорошо тренируете свой мозг и чувствуете себя комфортно при этом. Это ключ к пониманию Монады позже.
источник
Речь идет о типах : в первом случае вы задаете типы
String
(для компании и модели) иInt
для года. Во втором случае ваши более общие.a
,,b
иc
могут быть теми же типами, что и в первом примере, или совсем другими. Например, может быть полезно указать год в виде строки вместо целого числа. А если хотите, можете даже использовать свойColor
шрифт.источник