Здесь «значения», «типы» и «виды» имеют формальное значение, поэтому рассмотрение их общего использования на английском языке или аналогии с классификацией автомобилей только поможет вам.
Мой ответ относится к формальным значениям этих терминов в контексте Хаскелла; эти значения основаны (хотя на самом деле не идентичны) значениям, используемым в математической / CS "теории типов". Таким образом, это не будет очень хорошим ответом «информатики», но он должен послужить довольно хорошим ответом на Haskell.
В Haskell (и других языках) полезно назначить тип программному выражению, который описывает класс значений , которые разрешено иметь выражению. Здесь я предполагаю, что вы видели достаточно примеров, чтобы понять, почему было бы полезно знать, что в выражении sqrt (a**2 + b**2)
переменные a
и b
всегда будут значениями типа, Double
а не, скажем, String
и Bool
соответственно. По сути, наличие типов помогает нам в написании выражений / программ, которые будут корректно работать в широком диапазоне значений .
Теперь, что вы, возможно, не поняли, - это то, что типы Haskell, такие как те, которые появляются в сигнатурах типов:
fmap :: Functor f => (a -> b) -> f a -> f b
на самом деле написаны на подъязыке Haskell на уровне типов. Текст программы Functor f => (a -> b) -> f a -> f b
- в буквальном смысле - выражение типа, написанное на этом подъязыке. Подъязык входят операторы (например, ->
является правым ассоциативным инфиксный оператор на этом языке), переменные (например, f
, a
, и b
), и «приложение» одного выражения типа к другому (например, f a
это f
применяется к a
).
Я упоминал, как во многих языках было полезно назначать типы программным выражениям для описания классов значений выражений? Что ж, в этом подъязыке на уровне типов выражения оцениваются по типам (а не по значениям ), и в итоге оказывается полезным назначать виды выражениям типов для описания классов типов, которые им разрешено представлять. По сути, наличие видов помогает нам в написании выражений типов, которые будут корректно работать с широким диапазоном типов .
Таким образом, значения относятся к типам, а типы - к видам , а типы помогают нам писать программы уровня значения, а виды помогают нам писать программы типа уровня .
Как выглядят эти виды ? Хорошо, рассмотрим тип подписи:
id :: a -> a
Если выражение типа a -> a
должна быть действительными, какой вид из типов , мы должны позволить переменным a
быть? Ну, типа выражения:
Int -> Int
Bool -> Bool
выглядят корректно, поэтому типы Int
и Bool
, очевидно, правильного вида . Но даже более сложные типы, такие как:
[Double] -> [Double]
Maybe [(Double,Int)] -> Maybe [(Double,Int)]
смотри правильно. Фактически, поскольку мы должны иметь возможность вызывать id
функции, даже:
(a -> a) -> (a -> a)
выглядит хорошо. Таким образом, Int
, Bool
, [Double]
, Maybe [(Double,Int)]
, и a -> a
все выглядят как типы правого вида .
Другими словами, похоже, что есть только один вид , давайте назовем его *
подстановочным знаком Unix, и у каждого типа будет свой вид *
, конец истории.
Правильно?
Ну, не совсем. Оказывается, что Maybe
само по себе так же верно выражение типа, как и Maybe Int
(почти так же sqrt
, как само по себе, так же верно выражение значения, как и sqrt 25
). Однако следующее выражение типа недопустимо:
Maybe -> Maybe
Потому что, в то время как Maybe
это выражение типа, он не представляет вид из типа , который может иметь значение. Итак, вот как мы должны определить *
- это вид из типов , которые имеют значение; он включает в себя «полные» типы, такие как Double
или, Maybe [(Double,Int)]
но исключает неполные, бесполезные типы, такие как Either String
. Для простоты я буду называть эти полные типы вида *
«конкретными типами», хотя эта терминология не универсальна, и «конкретные типы» могут означать нечто совсем иное, скажем, для программиста C ++.
Теперь в выражении типа a -> a
, пока тип a
имеет тип *
(тип конкретных типов), результат выражения типа такжеa -> a
будет иметь тип (т. Е. Тип конкретных типов). *
Итак, что вид из типа является Maybe
? Ну, Maybe
может быть применен к конкретному типу, чтобы получить другой конкретный тип. Так, Maybe
выглядит как маленькая , как функция типа уровня , который принимает вид из рода *
и возвращает тип из вида *
. Если бы мы имели функцию на уровень значения, принявшее значение из типа Int
и возвращающее значение из типа Int
, мы бы дать ему тип подпись Int -> Int
, поэтому по аналогии мы должны дать Maybe
на любезную подпись * -> *
. GHCi соглашается:
> :kind Maybe
Maybe :: * -> *
Возвращаясь к:
fmap :: Functor f => (a -> b) -> f a -> f b
В сигнатуре этого типа переменная f
имеет вид, * -> *
переменные a
и b
вид *
; встроенный оператор ->
имеет вид * -> * -> *
(он принимает тип вида *
слева и один справа и возвращает тип также типа *
). Из этого и правил вывода вида можно сделать вывод, что a -> b
это допустимый тип с типом *
, f a
а f b
также допустимые типы с типом *
и (a -> b) -> f a -> f b
допустимый тип типа *
.
Другими словами, компилятор может «проверять вид» выражения типа, (a -> b) -> f a -> f b
чтобы убедиться, что он действителен для переменных типа правильного типа, так же, как он «проверяет тип», sqrt (a**2 + b**2)
чтобы убедиться, что он действителен для переменных правильного типа.
Причина использования отдельных терминов для «типов» и «видов» (т. Е. Не говорить о «типах») состоит в основном во избежание путаницы. Эти виды выше вид очень отличается от типов и, по крайней мере , на первый, кажется, ведут себя совершенно по- разному. (Например, требуется некоторое время, чтобы понять, что каждый «нормальный» тип имеет одинаковый тип, *
а тип a -> b
- *
нет * -> *
.)
Часть этого также историческая. По мере развития GHC Haskell различия между значениями, типами и типами начали стираться. В наши дни значения можно «преобразовать» в типы, а типы и типы - это одно и то же. Таким образом, в современном Haskell значения имеют как типы, так и типы ARE (почти), а типы - просто больше типов.
Пользователь @ user21820 попросил дать дополнительное объяснение: «типы и типы - это одно и то же». Чтобы быть более понятным, в современном GHC Haskell (я думаю, начиная с версии 8.0.1) типы и виды обрабатываются одинаково в большей части кода компилятора. Компилятор прилагает некоторые усилия в сообщениях об ошибках, чтобы различать «типы» и «виды», в зависимости от того, жалуется ли он на тип значения или тип типа соответственно.
Кроме того, если расширения не включены, они легко различимы на языке поверхности. Например, типы (значений) имеют представление в синтаксисе (например, в сигнатурах типов), но виды (типов) являются - я думаю - полностью неявными, и нет явного синтаксиса, где они появляются.
Но если вы включите соответствующие расширения, различие между типами и видами в значительной степени исчезнет. Например:
{-# LANGUAGE GADTs, TypeInType #-}
data Foo where
Bar :: Bool -> * -> Foo
Здесь Bar
есть (и значение, и) тип. Как тип, его тип есть Bool -> * -> Foo
, который является функцией уровня типа, которая принимает тип типа Bool
(который является типом, но также и типом) и типом типа *
и производит тип типа Foo
. Так:
type MyBar = Bar True Int
правильно проверок.
Как объясняет @AndrejBauer в своем ответе, эта неспособность различать типы и виды небезопасна - наличие типа / типа *
, тип / тип которого сам является (что имеет место в современном Haskell), приводит к парадоксам. Тем не менее, система типов Хаскелла уже полна парадоксов из-за отсутствия завершения, так что это не считается большой проблемой.