Типове и класове - Учи Haskell на име добро!
Вярвайте в типове

По-рано казахме, че Haskell е статично типизиран език. Типът на всеки израз е известен по време на компилиране, което води до безопасен код. Ако напишете програма, която се опитва да раздели булев тип на число, тя дори няма да се компилира. Това е добре, защото е по-добре да хванете такива грешки по време на компилиране, вместо програмата ви да се срине, докато работи. Всичко в Haskell има тип, така че компилаторът може да направи доста заключения за вашата програма, преди да я компилира.
За разлика от Java или Pascal, Haskell има механизъм за извеждане на типове. Ако напишем число, не е нужно да казваме на езика, че е число. Haskell може сам да изведе това, така че не е нужно изрично да въвеждаме нашите функции и изрази. Покрихме някои от основите на Haskell само с много бегло споменаване на типовете. Въпреки това разбирането на системата от типове е много важна част от изучаването на Haskell.
Сега нека използваме GHCi, за да определим типовете на няколко израза. Ще направим това с командата :t. Да опитаме.

Ако искате да декларирате типа на вашата функция, но не сте сигурни какво трябва да бъде, винаги можете да напишете функцията без него и след това да проверите типа на функцията с :t. Функциите също са изрази, така че :t ще работи с тях без проблеми.
А ето и преглед на някои често използвани типове.
Int означава цяло число. Използва се за цели числа. 7 може да бъде от тип Int, но 7.2 не е. Int Int е ограничен, което означава, че има минимална и максимална стойност. Обикновено на 32-битови машини най-големият възможен Int е 2147483647, а най-малкият възможен е -2147483648.
Цяло число означава, ъъъ... също цяло число. Основната разлика е, че няма ограничение, така че може да представя наистина големи числа. Имам предвид наистина голям. Междувременно Int е по-ефективен.
Float е реално число с плаваща запетая с единична точност.
Double е истинско число с плаваща запетая с двойна точност!
Bool е булев тип. Този тип може да приема само две стойности: True и False.
Char представлява символ. Те са разделени с единични кавички. Списъкът със знаци е низ.
те са типове, но типът на кортежа зависи от неговата дължина и от вида на неговите компоненти. Така че, теоретично, има безкраен брой типове кортежи - което е твърде много, за да ги изброим всички в този урок. Имайте предвид, че празен кортеж () също е тип, който може да съдържа една стойност: ()
Типови променливи
Какъв тип според вас е функцията на главата? head взема списък от произволен тип и връща първия елемент, така че какъв е неговият тип? Да проверим!
Въпреки че променливите на типа могатимат имена, състоящи се от повече от една буква, обикновено се наричат от a, b, c, d ...
Помните ли функцията fst? Връща първия компонент в двойката. Нека проверим вида му.
Можете да видите, че fst приема като параметър кортеж от два типа и връща елемент от същия тип като първия компонент на двойката. Следователно можем да приложим fst към двойка, която съдържа всеки два типа. Имайте предвид, че тъй като a и b са променливи от различен тип, те изобщо не трябва да обозначават различни типове. Тази нотация означава, че типовете на първия компонент и върнатата стойност са еднакви.
ABC на типовите класове

Клас тип е нещо като интерфейс, който дефинира някакво поведение. Ако даден тип е част от клас тип, това означава, че той поддържа и прилага поведението, описано от този клас. Много хора, идващи от ООП, се объркват относно класовете от типове, защото смятат, че са подобни на класовете в обектно-ориентираните езици. Всъщност те изобщо не си приличат. Можете да мислите за тях като интерфейси в Java, само че по-добри.
Какъв е подписът на типа за функцията ==?
Класът тип Eq предоставя интерфейс за тестване на равенството. Всеки тип, за чиито стойности операцията за равенство има смисъл, трябва да бъде член на класа Eq.Всички стандартни типове на Haskell, с изключение на IO (тип за работа с вход и изход) и с изключение на функции, са в класа тип Eq.
Функцията elem има тип (Eq a) => a-> [a]-> Bool, тъй като използва оператора == върху елементите на списък, за да провери дали списъкът съдържа стойността, която търсим.
Няколко основни типове класове:
Eq се използва за типове, които поддържат тестване за равенство. Този тип интерфейс реализира двефункции - == и /= . Така че, ако имаме ограничение на клас Eq върху променлива тип във функция, тогава тя може да използва == или /= в дефиницията си. Всички типове, които споменахме по-рано, с изключение на функциите, са включени в Eq и следователно могат да бъдат тествани за равенство.
Ord е за типове, които поддържат подреждане.
Всички типове, споменати по-рано, с изключение на функциите, са част от Ord. Ord съдържа всички стандартни функции за сравнение като > , , >= и . . Функцията за сравнение взема два члена на Ord от един и същи тип и връща връзката между тях. Типът подреждане може да приема стойностите GT, LT или EQ, което означава съответно по-голямо от, по-малко от и равно на.
За да стане член на Ордена, типът трябва първо да има членство в престижния и изключителен клуб Eq.
Членовете на класа Show type могат да бъдат представени като низове. Всички типове, описани по-рано, с изключение на функциите, са част от Show. Най-използваната функция в класа Show type е функцията show. Той приема стойност, чийто тип е Show и я представя като низ.
Read е обратното на класа Show type. Функцията read взема низ и връща тип, който е член на Read.
Страхотен. Още веднъж, всички типове, описани по-рано, са включени в този клас типове. Но какво се случва, ако се опитате да прочетете "4"?
Това се опитва да ни каже GHCI, че не знае какво точно искаме да получим като резултат. Обърнете внимание, че по време на предишните извиквания за четене след това направихме нещо с резултата от функцията. По този начин GHCI можеше да разбере какъв тип отговор искаме от прочетено Когато използвахме резултата като булево, той знаеше да върне Bool. И в този случай той знае, че ниенеобходим е някакъв тип, който е част от класа Read, но не знае кой. Нека да разгледаме сигнатурата на функцията за четене.
виждаш ли Функцията връща тип, който е част от Read, но ако не го използваме по-късно, компилаторът няма начин да определи кой тип е той. Ето защо се използват изрични анотации за тип. Анотациите за тип са начин изрично да се укаже какъв тип трябва да бъде изразът. Това става чрез добавяне на :: в края на израза и указване на типа. Вижте:
За повечето изрази компилаторът може сам да изведе типа. Но понякога не знае дали да върне Int или Float за израз като read "5" . За да види какъв е типът, Haskell трябва действително да оцени read "5" . Но тъй като Haskell е статично типизиран език, той трябва да познава всички типове, преди кодът да бъде компилиран (или в случая на GHCI, оценен). Така че трябва да кажем на Haskell: „Хей, този израз трябва да има този тип, в случай че не знаете!“.
Членовете на Enum са последователно подредени типове - те могат да бъдат изброени. Основното предимство на типа Enum >succ и pred функции. Въведете в това >(), Bool, Char, Ordering, Int, Integer, Float и Double.
Ограничените членове имат горна и долна граница.
minBound и maxBound са интересни, защото имат тип (Bounded a) => а. В известен смисъл те са полиморфни константи.
Всички кортежи също са част от Bounded, ако компонентите също са в него.
Num е числов тип клас. Неговите членове имат свойството да могат да действат като числа. Нека разгледаме вида на числото.
Изглежда, че целите числа също са полиморфни константи. Те могат да действат като всеки тип, който е член на тип класа Num.
Това са типове, които са в 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 -> б . От сигнатурата на типа виждаме, че той взема интегрално число и го превръща в по-общо число. Това е полезно, когато искате интегралните типове и типовете с плаваща запетая да работят добре заедно. Например, функцията length има декларация за тип length :: [a] -> Int вместо да има по-общ тип (Num b) => дължина :: [a] -> б . Мисля, че това е по исторически причини или нещо подобно, въпреки че по мое мнение е доста тъпо >3.2, ще получим грешка, защото се опитахме да съберем Int и число с плаваща запетая. Така че, за да заобиколим това, ние правим fromIntegral (дължина [1,2,3,4]) + 3.2 и всичко работи.
Забележете, че fromIntegral има няколко ограничения на класа в сигнатурата на типа. Това е напълно валидно и както можете да видите, ограниченията на класа са разделени със запетаи в скобите.