Типи і класи - Вивчіть Haskell ім’ям добре!
Повір у типи

Раніше ми вже говорили, що Haskell є статично типізованою мовою. Тип кожного виразу відомий під час компіляції, що призводить до безпечного коду. Якщо ви напишете програму, яка спробує поділити булевський тип на число, вона навіть не скомпілюється. Це добре, тому що краще ловити такі помилки на етапі компіляції замість того, щоб ваша програма падала під час роботи. Все в Haskell має свій тип, тому компілятор може зробити досить багато висновків про вашу програму перед її компіляцією.
На відміну від Java або Pascal, Haskell має механізм виведення типів. Якщо ми напишемо число, то не треба говорити мови, що це число. Haskell може вивести це сам, тому нам не треба явно вказувати типи наших функцій і виразів. Ми вивчили деякі основи Haskell лише дуже поверхово згадавши типи. Проте розуміння системи типів є дуже важливою частиною навчання мови Haskell.
А зараз скористаємося GHCi для визначення типів кількох виразів. Ми зробимо це за допомогою команди :t. Давайте спробуєм.

Якщо ви хочете оголосити тип вашої функції, але не впевнені, яким він має бути, завжди можна написати функцію без нього, а потім перевірити тип функції за допомогою :t . Функції теж висловлювання, так що :t працюватиме з ними без проблем.
А ось огляд деяких типів, що часто використовуються.
Int означає ціле число. Він використовується для цілих чисел. 7 може бути типу Int, але 7.2 — ні. Int Int обмежений, і це означає, що він має мінімальне і максимальне значення. Зазвичай, на 32-бітних машинах максимально можливий Int - це 2147483647, а мінімально можливий -2147483648.
Integer означає еээ… теж ціле число. Основна різниця в тому, що він не має обмеження, тому він може представляти справді великі числа. Мається на увазі справді великі. Тим часом, Int ефективніший.
Float – це речове число з плаваючою точкою одинарної точності.
Double – це речове число з плаваючою точкою з подвоєною точністю!
Bool це булевський тип. Цей тип може приймати лише два значення: True та False .
Char представляє символ. Їх виділяють одинарними лапками. Список символів – це рядок.
це типи, але тип кортежу залежить від його довжини та від типу його компонентів. Отже, теоретично, існує нескінченна кількість типів кортежів — а це забагато, щоб перерахувати їх у цьому посібнику. Зауважте, що порожній кортеж () — це також тип, який може містити єдине значення: ()
Змінні типу
Як ви вважаєте, який тип у функції head ? head приймає список будь-якого типу і повертає перший елемент, то який у неї тип? Давайте перевіримо!
Незважаючи на те, що змінні типу можутьмати імена, що складаються з більш ніж однієї літери, зазвичай вони називаються of a, b, c, d …
Пам'ятаєте функцію fst? Вона повертає перший компонент у парі. Давайте перевіримо її тип.
Можна помітити, що fst приймає як параметр кортеж, який складається з двох типів, і повертає елемент того самого типу як перший компонент пари. Тому ми можемо застосувати fst до пари, яка містить два типи. Зауважте, що через те, що a і b різні змінні типу, вони не повинні позначати різні типи. Такий запис позначає, що типи першого компонента і значення, що повертається однакові.
Абетка класів типів

Клас типів — це щось подібне до інтерфейсу, який визначає деяку поведінку. Якщо тип є частиною класу типів, це означає, що він підтримує та реалізує поведінку, що описується цим класом. Багато людей, які приходять з ОВП, плутаються в класах типів, тому що думають, що вони схожі на класи в об'єктно-орієнтованих мовах. Взагалі вони зовсім не схожі. Можете думати про них як про інтерфейси Java, тільки краще.
Яка сигнатура типу для функції ==?
Клас типу Eq надає інтерфейс перевірки на рівність. Кожен тип, для значень якого операція перевірки на рівність має сенс, повинен бути членом класу Eq Усі стандартні типи Хаскеля, крім IO (тип для роботи з введенням та виведенням) та за винятком функцій – входять до класу типів Eq .
Функція elem має тип (Eq a) => a -> [a] -> Bool , тому що вона використовує оператор == над елементами списку, щоб перевірити, чи є в цьому списку значення ми шукаємо.
Декілька базових класів типів:
Eq використовується для типів, які підтримують перевірку рівності. Інтерфейс цього типу реалізує двіфункції - == і /=. Так що якщо ми маємо обмеження класу Eq для змінної типу в функції, то вона може використовувати == або /= всередині свого визначення. Всі типи, які ми згадували раніше, за винятком функцій, входять в Eq , і, отже, можуть бути перевірені на рівність.
Ord призначений для типів, що підтримують упорядкування.
Усі типи згадані раніше, крім функцій, є частиною Ord . Ord містить усі стандартні функції порівняння, такі як > , , >= і . . Функція порівняння приймає два члени Ord того самого типу, і повертає ставлення порядку з-поміж них. Тип Ordering може приймати значення GT , LT or EQ , означаючи, відповідно, "більше", "менше ніж" і "рівно".
Щоб стати членом Ord, тип повинен спочатку мати членство в престижній та ексклюзивному клубі Eq.
Члени класу типів Show можуть бути як рядки. Всі типи, описані раніше, крім функцій, є частиною Show . Найбільш використовується функція в класі типів Show - це функція show. Вона бере значення, чий тип належить Show і представляє його у вигляді рядка.
Read – це щось протилежне класу типів Show. Функція read приймає стоку та повертає тип, який є членом Read.
Чудово. Ще раз повторюю, всі описані раніше типи входять до цього класу типів. Але що станеться, якщо спробувати зробити read "4"?
Це GHCI намагається нам сказати, що він не знає, що саме ми хочемо отримати в результаті. Зауважте, що під час попередніх дзвінків read ми потім щось робили з результатом функції. Таким чином, GHCI міг визначити, який тип відповіді з read ми хочемо отримати.Коли ми використовували результат як boolean, він знав, що треба повернути Bool . А в цьому випадку він знає, що нампотрібен якийсь тип, що входить до класу Read, але не знає який саме. Давайте подивимося на сигнатуру функції read.
Бачите? Функція повертає тип є частиною Read , але якщо ми не скористаємося ним пізніше, то компілятор не матиме способу визначити який саме це тип. Ось чому використовуються явні інструкції типу. Інструкції типу - це спосіб явно вказати, якого типу має бути вираз. Робиться це за допомогою додавання :: в кінець виразу та вказівки типу. Дивіться:
Більшість виразів компілятор може вивести тип самостійно. Але іноді він не знає, чи повернути значення типу Int або Float для вираження, на кшталт read "5". Для того, щоб бути типом, Haskell може бути актуально оцінити read "5" . Але Haskell є статичним типовим мовою, він має знати всі типи перед кодом є складеним (або в випадку GHCI, оцінений). Коли ми хотіли б Haskell: "Гей, цей вираз повинен мати цей тип, у випадку ви не знаєте!".
Enum members are sequentially ordered types — they can be enumerated. Основна перевага типу Enum >succ and pred functions. Types in this (), Bool, Char, Ordering, Int, Integer, Float and Double.
Bounded members має upper and a lower bound.
minBound and maxBound are interesting because вони мають тип (Bounded a) => a. In sin sense є polymorphic constants.
Всі tuples є також частиною основи, якщо компоненти є also in it.
Num is a numeric typeclass. Вони мають свою особливість, щоб бути здатною до числа номерів. Let's examine the type of a number.
Це видно, що всі номери є також polymorphic constants. Вони можуть діяти як будь-який тип, що є членом номера типукласу.
Those are types that are in the Numвведіть >* , ми побачимо, що він приймає всі числа.
Він приймає два числа одного типу та повертає число цього типу. Ось чому (5 :: Int) * (6 :: Integer) призведе до помилки типу, тоді як 5 * (6 :: Integer) працюватиме нормально та створюватиме Integer, оскільки 5 може діяти як Integer або Int .
Щоб приєднатися до Num, тип має бути друзями Show і Eq.
Інтеграл також є числовим типом >Num включає всі числа, включаючи дійсні числа та цілі числа, Інтеграл включає лише цілі (цілі) числа. У цьому типі >Int і Integer .
Плаваюча кома включає лише числа з плаваючою комою, тому Float і Double .
Дуже корисною функцією для роботи з числами є fromIntegral. Він має оголошення типу fromIntegral :: (Num b, Integral a) => a -> b . З сигнатури типу ми бачимо, що він бере ціле число та перетворює його на більш загальне число. Це корисно, коли ви хочете, щоб інтегральні типи та типи з плаваючою комою добре працювали разом. Наприклад, функція length має оголошення типу length :: [a] -> Int замість більш загального типу (Num b) => довжина :: [a] -> b . Я думаю, що це з історичних причин чи щось подібне, хоча, на мій погляд, це досить дурне >3.2, ми отримаємо помилку, оскільки ми намагалися додати разом Int і число з плаваючою комою. Отже, щоб обійти це, ми робимо fromIntegral (довжина [1,2,3,4]) + 3.2 і все працює.
Зауважте, що fromIntegral має кілька обмежень класу в сигнатурі типу. Це абсолютно правильно, і, як бачите, обмеження класу розділені комами в дужках.