В чем разница между ключевыми словами "data" и "type"?

Ключевые слова data и type всегда меня путают.

Я хочу знать, в чем разница между data и type и как их использовать.

Ответ 1

type объявляет синоним типа. Синоним типа - это новое имя для существующего типа. Например, вот как String определяется в стандартной библиотеке:

type String = [Char]

String - это другое имя для списка Char. GHC заменит все использование String в вашей программе на [Char] во время компиляции.

Чтобы быть ясным, String буквально является списком Char. Это просто псевдоним. Вы можете использовать все стандартные функции списка для значений String:

-- length :: [a] -> Int
ghci> length "haskell"
7
-- reverse :: [a] -> [a]
ghci> reverse "functional"
"lanoitcnuf"

data объявляет новый тип данных, который, в отличие от синонима типа, отличается от любого другого типа. Типы данных имеют ряд конструкторов, определяющих возможные варианты вашего типа. Например, вот как Bool определяется в стандартной библиотеке:

data Bool = False | True

Значение Bool может быть как True и False. Типы данных поддерживают сопоставление с шаблоном, что позволяет вам выполнять анализ случая во время выполнения для значения типа данных.

yesno :: Bool -> String
yesno True = "yes"
yesno False = "no"

Типы data могут иметь несколько конструкторов (как в Bool), могут параметризоваться другими типами, могут содержать внутри себя другие типы и могут рекурсивно ссылаться на себя. Вот модель исключений, которая демонстрирует это; Error a содержит сообщение об ошибке типа a и, возможно, ошибку, вызвавшую его.

data Error a = Error { value :: a, cause :: Maybe (Error a) }
type ErrorWithMessage = Error String

myError1, myError2 :: ErrorWithMessage
myError1 = Error "woops" Nothing
myError2 = Error "myError1 was thrown" (Just myError1)

Важно понимать, что data объявляют новый тип, который отличается от любого другого типа в системе. Если бы String был объявлен как тип data содержащий список Char (а не синоним типа), вы не сможете использовать какие-либо функции списка для него.

data String = MkString [Char]
myString = MkString ['h', 'e', 'l', 'l', 'o']
myReversedString = reverse myString  -- type error

Есть еще один newtype объявления типа: newtype. Это работает скорее как объявление data - оно вводит новый тип данных отдельно от любого другого типа и может соответствовать шаблону - за исключением того, что вы ограничены одним конструктором с одним полем. Другими словами, newtype является data типа, который оборачивает существующий тип.

Важным отличием является стоимость newtype: компилятор обещает, что newtype представлена таким же образом, как тип он заворачивает. Там нет времени выполнения для упаковки или распаковки newtype. Это делает newtype полезным для административных (а не структурных) различий между ценностями.

newtype хорошо взаимодействует с классами типов. Например, рассмотрим Monoid, класс типов с возможностью объединения элементов (mappend) и специального "пустого" элемента (mempty). Int можно превратить в Monoid разными способами, включая сложение с 0 и умножение на 1. Как мы можем выбрать, какой из них использовать для возможного экземпляра Monoid Int? Лучше не выражать предпочтения, а использовать newtype чтобы включить любое использование без затрат времени выполнения. Перефразируя стандартную библиотеку:

-- introduce a type Sum with a constructor Sum which wraps an Int, and an extractor getSum which gives you back the Int
newtype Sum = Sum { getSum :: Int }
instance Monoid Sum where
    (Sum x) 'mappend' (Sum y) = Sum (x + y)
    mempty = Sum 0

newtype Product = Product { getProduct :: Int }
instance Monoid Product where
    (Product x) 'mappend' (Product y) = Product (x * y)
    mempty = Product 1

Ответ 2

type работает так же, как let: он позволяет вам присвоить имя для повторного использования, но что-то всегда будет работать так же, как если бы вы ввели определение. Так

type ℝ = Double

f :: ℝ -> ℝ -> ℝ
f x y = let x2 = x^2
        in x2 + y

ведет себя точно так же, как

f' :: Double -> Double -> Double
f' x y = x^2 + y

как в: вы можете в любом месте вашего кода заменить f на f' и наоборот; ничего не изменится.

OTOH, как data, так и newtype создают непрозрачную абстракцию. Они больше похожи на конструктор классов в OO: хотя некоторое значение реализуется просто с точки зрения одного числа, оно не обязательно ведет себя как такое число. Например,

newtype Logscaledℝ = LogScaledℝ { getLogscaled :: Double }

instance Num LogScaledℝ where
  LogScaledℝ a + LogScaledℝ b = LogScaledℝ $ a*b
  LogScaledℝ a - LogScaledℝ b = LogScaledℝ $ a/b
  LogScaledℝ a * LogScaledℝ b = LogScaledℝ $ a**b

Здесь, хотя Logscaledℝ по-прежнему является только номером Double, он явно отличается от Double.

Ответ 3

С помощью data вы создаете тип данных new и объявляете для него конструктор:

data NewData = NewDataConstructor

С type вы определяете только псевдоним:

type MyChar = Char

В случае type вы можете передать значение типа MyChar для функции, ожидающей Char и наоборот, но вы не можете сделать это для data MyChar = MyChar Char.