Хаскелл типа против конструктора данных


Я учусь у Хаскелла 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 96

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, который является двоичным деревом, которое хранит 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 конкретный тип и возвращает конкретный тип.

в очередной раз! Разница между конкретным типом и функцией конструктора типа. Вы не можете создать список MaybeS - Если вы попытаетесь выполнить

[] :: [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 тип.