Ключевые слова data
и type
всегда меня путают.
Я хочу знать, в чем разница между data
и type
и как их использовать.
Ключевые слова data
и type
всегда меня путают.
Я хочу знать, в чем разница между data
и type
и как их использовать.
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
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
.
С помощью data
вы создаете тип данных new и объявляете для него конструктор:
data NewData = NewDataConstructor
С type
вы определяете только псевдоним:
type MyChar = Char
В случае type
вы можете передать значение типа MyChar
для функции, ожидающей Char
и наоборот, но вы не можете сделать это для data MyChar = MyChar Char
.