Яка різниця між типом і видом?


26

Я вивчаю мов програмування Haskell, і я намагаюся обернути голову навколо того, в чому полягає різниця між a typeі a kind.

Як я розумію, a kind is a type of type. Наприклад, a ford is a type of carі a car is a kind of vehicle.

Це хороший спосіб подумати над цим?

Тому що спосіб мого мозку в даний час є провідним ford is a **type** of car, але, але в car is a **type** of vehicleтой же час a car is a **kind** of vehicle. Тобто терміни typeі kindє взаємозамінними.

Чи може хтось пролити на це світло?


6
Я щойно прийшов сюди з поста про стек переповнення, який призвів до цієї дискусії. Я не впевнений, що я кваліфікований, щоб відповісти детально - але ви, безумовно, занадто буквально ставитеся до термінів "тип" і "вид", намагаючись співвідносити їх зі значенням англійською мовою (де вони справді є синонімами ). Ви повинні ставитися до них як до технічних термінів. Я вважаю, що "тип" добре зрозумілий всім програмістам, оскільки ця концепція є життєво важливою для кожної мови, навіть слабко типізованих, як Javascript, "Kind" - це технічний термін, що використовується в Haskell для "типу типу". Це дійсно все, що там є.
Робін Зігмонд

2
@RobinZigmond: ти правий, що це технічні терміни, але вони використовуються ширше, ніж просто в Haskell. Може бути зворотне посилання на дискусію щодо переповнення стека, яка породила це питання?
Андрій Бауер

@AndrejBauer Я ніколи не говорив, що вони не використовувалися за межами Haskell, звичайно, "тип" використовується, по суті, в кожній мові, як я вже говорив. Я ніколи насправді не натрапляв на "добрий" за межами Haskell, але тоді Haskell є єдиною функціональною мовою, яку я взагалі знаю, і я був обережним, щоб не сказати, що термін не використовується в іншому місці, тільки що він використовується таким чином в Хаскелл. (І посилання, як Ви просите, тут )
Робін Зігмонд

Мови сімейства ML також мають різні види, наприклад, стандарт ML та OCaml. Я думаю, вони не піддаються прямому імені. Вони проявляються як підписи , а їх елементи називаються структурами .
Андрій Бауер

1
Більш точна англійська аналогія - Ford - це тип автомобіля, а автомобіль - це тип транспортних засобів, але і типи автомобілів, і типи транспортних засобів мають однаковий вид: іменники. Тоді як червоний - це тип кольору автомобіля, а RPM - це тип показників продуктивності автомобіля, і обидва мають однаковий вид: прикметники.
slebetman

Відповіді:


32

Тут "значення", "типи" та "види" мають формальне значення, тому врахування їх загального використання в англійській мові або аналогій з класифікацією автомобілів ви отримаєте лише поки що.

Моя відповідь стосується формальних значень цих термінів конкретно в контексті Хаскелла; ці значення базуються на (хоча насправді не є тотожними) значеннями, використаними в математичній / 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 вже повна парадоксів через неприпинення, тому це не вважається великою справою.


Якщо "типи і види справді одне і те ж", то тип - typeце просто typeсам, і в цьому взагалі не було б потреби kind. То в чому саме полягає відмінність?
користувач21820

1
@ user21820, я додав до кінця примітку, яка може вирішити цю проблему. Коротка відповідь: насправді немає відмінності в сучасному GHC Haskell .
KA Buhr

1
Це чудова відповідь - дуже дякую за спільний доступ. Це добре написано і вводить поняття поступово - так як хтось, хто не писав Haskell кілька років, це дуже цінується!
ultrafez

@KABuhr: Дякую за доданий біт!
користувач21820

19

Якщо ви знаєте про різницю між множинами та класами в теорії множин, то це може допомогти подумати над питанням як

type:kind=set:class.

  • Bool - тип
  • Type є різновидом, оскільки його елементи - типи
  • Bool -> Int - тип
  • Bool -> Type є різновидом, оскільки його елементи - це функції, які повертають типи
  • Bool * Int - тип
  • Bool * Type є різновидом, оскільки його елементи є парними з одним компонентом типу

U0U1U2U0BoolNatNatNatU1U0BoolU0U0U0Un+1UnUn× тощо.

U0U1U0U1U0**U_1


2
Я не думаю, що (GHC) у Haskell немає жодної концепції всесвітів. Type :: Typeє аксіомою. Відмінність між "типом" і "родом" повністю в людській мові. Trueмає тип, Boolі Boolмає тип Type, який сам має тип Type. Іноді ми називаємо тип родом, щоб підкреслити, що це тип сутності типу типу, але, в Haskell, це все-таки лише тип. У системі, де насправді існують всесвіти, як Coq, тоді "тип" може посилатися на один Всесвіт, а "добрий" на інший, але тоді ми зазвичай хочемо нескінченно багато всесвітів.
HTNW

1
Відмінність - це не просто "людська мова", це формальне розрізнення в системі, що лежить в основі. Цілком можливо існувати Type :: Typeі розрізнення між видами і видами. Також, який фрагмент коду демонструється Type :: Typeв Haskell?
Андрій Бауер

1
Я також повинен сказати, що *в Haskell - це своєрідний Всесвіт. Вони просто не називають це так.
Андрій Бауер

3
@AndrejBauer Typeвід Data.Kindsі *має бути синонімами. Спочатку ми мали лише *як примітив, в той час як сьогодні це визначається як GHC.Types.Typeвнутрішній модуль GHC.Types, у свою чергу визначається як type Type = TYPE LiftedRep. Я думаюTYPE це справжній примітив, що забезпечує сімейство видів (типів, що піднімаються, типи без коробки, ...). Більшість «неелегантних» складностей тут полягає в підтримці деяких оптимізацій низького рівня, а не з теоретичних причин фактичного типу.
чі

1
Спробую підсумувати. Якщо vце значення, то воно має вигляд: v :: T. Якщо Tце тип, то вона має вигляд: T :: K. Тип типу називають його видом. Типи, схожі на те, TYPE repможна назвати сортуваннями, хоча це слово є рідкісним. ІФФ T :: TYPE repбуде Tдозволено з'являтися на РІТ з ::. Слово "вид" має його нюанс: Kin T :: Kє різновидом, але не в v :: K, хоча це те саме K. Ми могли б визначити " Kє різновид, якщо його різновид є свого роду" aka "види є на RHS ::", але це не фіксує використання правильно. Тому моя "людська відмінність" позиція.
HTNW

5

Значення , як специфічний червоний 2011 Ford Mustang з 19,206 миль на ньому , що ви сиділи в дорозі.

Такого конкретного значення, неофіційно, може бути багато типів : це Mustang, і це Ford, і це машина, і це транспортний засіб, серед багатьох інших типів, які ви могли б скласти (тип "речей" належать вам ", або тип" речей, які червоні ", або ...).

(В Haskell, наближення першого порядку (GADT розбиває цю властивість, а магія навколо літералів чисел та розширення OverloadedStrings трохи затьмарює її), значення мають один головний тип замість безлічі неформальних "типів", які ви можете дати своїм " станг. 42 - для цілей цього пояснення - Intнемає; в Haskell немає типу для "чисел" або "навіть цілих чисел" - або, скоріше, ви можете зробити одне, але це буде неперервний тип від Int.)

Тепер "Мустанг" може бути підтипом "автомобіль" - кожне значення, яке є Мустангом, також є автомобілем. Але тип - або, щоб використовувати термінологію Haskell, вид "Mustang" не є "автомобілем". Тип "Мустанг" - це не річ, яку можна припаркувати на дорозі або проїхати. "Мустанг" - це іменник, або категорія, або просто тип. Це, неофіційно, види "Мустанг".

(Знову ж таки, Хаскелл розпізнає лише один вид для кожної речі на рівні типу. Так само Intмає вид *, і жоден інший вид. Не Maybeмає роду * -> *і жодного іншого виду. Але інтуїція все-таки має місце: 42є Int, і ви можете робити Intз цим речі . , як додавання і віднімання Intсам по собі не є Int, немає такого числа , як Int + IntВи можете неформально чути , як люди кажуть , що. Intце Num, за якими вони розуміють , що є екземпляр з Numкласу типу для типу Int-Ети не те ж саме як сказати, що Intмає вид Num . Intмає вид "типу", що в Haskell написано *.)

Тож чи не кожен неофіційний "тип" - це лише іменник чи категорія? Чи всі типи мають однаковий вид? Навіщо взагалі говорити про види, якщо вони такі нудні?

Тут англійська аналогія вийде трохи кам’янистою, але потерпіть мене: зробіть вигляд, що слово "власник" англійською мовою не мало сенсу ізольовано, без опису речі, що належить їй. Притворіться, що якби хтось назвав вас "власником", це взагалі не мало б для вас сенсу; але якщо хтось назвав вас "власником машини", ви могли б зрозуміти, що вони означають.

"Власник" робить не має такого типу, як "автомобіль", тому що ви можете говорити про автомобіль, але ви не можете говорити про власника в цій створеній англійській версії. Можна говорити лише про "власника автомобіля". "Власник" створює лише щось своєрідне "іменник", коли воно застосовується до чогось, що вже має добрий "іменник", наприклад "автомобіль". Ми б сказали, що вид "власник" - це "іменник -> іменник". "Власник" - це як функція, яка бере іменник і виробляє з цього інший іменник; але це не сам іменник.

Зауважте, що "власник автомобіля" не є підтипом "автомобіля"! Це не функція, яка приймає або повертає машини! Це просто зовсім окремий тип від "машини". Він описує цінності з двома руками та двома ногами, які в один момент мали певну суму грошей, і брали ці гроші в автосалон. Він не описує значення, які мають чотири колеса та завдання фарби. Також зауважте, що "власник автомобіля" та "власник собаки" є різними типами, і речі, які ви могли б хотіти зробити з одним, можуть не застосовуватися до іншого.

(Так само, коли ми говоримо, що в Хаскеллі Maybeє такий вид * -> *, ми маємо на увазі, що безглуздо (формально; неофіційно ми це робимо постійно) говорити про те, що маємо "а Maybe". Натомість ми можемо мати " Maybe Intабо" Maybe String, оскільки це речі вид *.)

Таким чином, вся суть розмови про види взагалі полягає в тому, щоб ми могли формалізувати своє міркування навколо таких слів, як "власник", і домогтися того, що ми лише коли-небудь приймаємо значення типів, які були "повністю побудовані" і не є безглуздими.


1
Я не кажу, що ваша аналогія неправильна, але я думаю, що це може викликати плутанину. У Dijkstra є кілька слів про аналогії. Google "Про жорстокість дійсно викладання обчислювальної науки".
Рафаель Кастро

Я маю на увазі, є автомобільні аналогії, а потім є аналоги автомобілів. Я не думаю, що виділення неявної структури типу природною мовою (яку, правда, я розтягнув у другій половині) як спосіб пояснення системи формального типу - це такий самий вид аналогії навчання, як і розмова що програма "хоче" зробити.
користувач11228628

1

Як я розумію, різновид - це тип типу.

Це правильно - тому давайте вивчимо, що це означає. Intабо Textконкретні типи, але Maybe aце абстрактний тип. Він не стане конкретним типом, поки ви не вирішите, яке саме значення потрібно для aпевної змінної (або значення / вираз / будь-яке), наприклад Maybe Text.

Ми говоримо, що Maybe aце конструктор типу, тому що це як функція, яка приймає єдиний тип бетону (наприклад Text) і повертає конкретний тип ( Maybe Textв даному випадку). Але інші конструктори типу можуть зайняти ще більше «парам входів», перш ніж вони повернуть конкретний тип. наприклад, Map k vпотрібно взяти два типи бетону (наприклад, Intі Text), перш ніж він зможе побудувати конкретний тип ( Map Int Text).

Отже, конструктори Maybe aта List aтип мають той самий "підпис", який ми позначаємо, як * -> *(аналогічно підпису функції Haskell), тому що якщо ви дасте їм один конкретний тип, вони виплюнуть конкретний тип. Ми називаємо це «видом» типу Maybeі Listмаємо той самий вид.

Кажуть, що конкретні типи мають добрий характер *, а наш приклад на карті - добрий* -> * -> * оскільки він використовує два типи бетону як вхідні дані, перш ніж він може вивести конкретний тип.

Ви можете бачити, що це стосується переважно кількості "параметрів", які ми передаємо конструктору типів, але розуміємо, що ми також можемо отримати конструктори типів, вкладені всередину конструкторів типів, тому ми можемо закінчити такий вид, який виглядає як* -> (* -> *) -> * наприклад, .

Якщо ви - розробник Scala / Java, ви можете також вважати це пояснення корисним: https://www.atlassian.com/blog/archives/scala-types-of-a-higher-kind


Це неправильно. У Хаскеллі ми розрізняємо Maybe aсинонім forall a. Maybe aполіморфного типу *і Maybeмономорфний тип роду * -> *.
b0fh
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.