Тут "значення", "типи" та "види" мають формальне значення, тому врахування їх загального використання в англійській мові або аналогій з класифікацією автомобілів ви отримаєте лише поки що.
Моя відповідь стосується формальних значень цих термінів конкретно в контексті Хаскелла; ці значення базуються на (хоча насправді не є тотожними) значеннями, використаними в математичній / CS "теорії типів". Отже, це не буде дуже хорошою відповіддю "інформатики", але це має слугувати досить гарною відповіддю Хаскелла.
У 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
).
Чи згадував я, як було корисно на багатьох мовах призначати типи програмним виразам для опису класів значень виразів? Ну, у цьому підмові на рівні типу вирази оцінюються за типами (а не значеннями ), і в кінцевому підсумку корисно призначати видам виразів для опису класів типів, які вони можуть представляти. В основному, наявність видів допомагає нам писати вирази типів, які працюватимуть правильно для широкого спектру типів .
Таким чином, значення є типами , як типи є види і типи допомагають нам писати вартість -LEVEL програм в той час як види допомагають нам писати типу -LEVEL програм.
Як виглядають ці види ? Ну, врахуйте підпис типу:
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)
щоб перевірити, чи він дійсний для змінних потрібного типу.
Причина використання окремих термінів для "типів" проти "видів" (тобто, не кажучи про "типи типів") здебільшого полягає лише в тому, щоб уникнути плутанини. The види вище вид дуже відрізняється від типів і, по крайней мере , на перший, здається, поводяться зовсім по- різному. (Наприклад, потрібен якийсь час , щоб обернути навколо голови думки , що кожен «нормальний» тип має той же вигляд *
і вигляд 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) призводить до парадоксів. Однак система типу Haskell вже повна парадоксів через неприпинення, тому це не вважається великою справою.