Система типов в Haskell

Поговорим немного об основах системы типов в Haskell. Как она устроена, какие преимущества дает и как с ней работать.

Простые типы

Названия типов в Haskell всегда пишутся с заглавной буквы. Есть несколько простых типов:

  • Int - целые числа (максимальное и минимальное значение определяется размером машинного слова на компьютере).
  • Integer - целые числа без ограничения на длину. Может быть на столько большим, на сколько хватит памяти компьютера.
  • Float - числа с плавающей точкой.
  • Double - числа с плавающей точкой двойной точности.
  • Bool - логический тип. Может принимать только значения True и False (обязательно с заглавной буквы). В Haskell булевые типы не имеют цифровых аналогов т.е. True и 1 это не одно и то же.
  • Char - символы. Указываются в одинарных кавычках. Haskell оперирует символами в юникоде.
  • String - строки. По сути в Haskell строки - это списки символов, как в Си. String является алиасом для типа [Char]. Можно использовать любой из них.
  • () - пустой кортеж. Кортежи сами по себе не являются отдельным типом, за исключением пустого кортежа. Пустой кортеж определяется как () и это его единственное значение.

Переменные типа

Так как Haskell имеет статическую строгую типизацию, для комфортной работы необходима возможность описывать полиморфные функции - функции, которые могут работать с несколькими типами данных. В языках вроде Java для этого используются дженерики (generics).

В Haskell есть так называемые типовые переменные. Мы просто вместо конкретного типа указываем его псевдоним. Псевдоним для типовой переменной может быть любой строкой, начинающейся со строчной буквы. Но чаще всего их называют одной-двумя буквами. Например функция head, которая возвращает головной элемент списка имеет следующую сигнатуру: head :: [a] -> a. Она принимает список элементов любого типа и возвращает одно значение такого же типа.

Важно знать, что переменные типа с разными именами не обязательно должны иметь значения разных типов. Например функция fst, которая возвращает первый элемент кортежа имеет сигнатуру fst :: (a, b) -> a. При этом оба вызова будут корректны: fst (1, 2) и fst(1, 'g').

Классы типов

Важно не путать классы типов с классами из ООП. Классы типов можно назвать аналогом Java интерфейсов. Класс типов определяет набор функций, которые должны реализовать типы, являющиеся его экземплярами. Как и в случае с Java интерфейсами, тип может реализовать один или несколько классов.

Например класс типов Ord, для упорядоченных типов. Он обязывает реализовать функцию (>). Сигнатура этой функции выглядит следующим образом: (>) :: (Ord a) => a -> a -> Bool. Функция принимает два аргумента одного типа a и возвращает булево значение. В первой части объявления мы видим ограничение класса - часть находящуюся слева от символа =>. Оно говорит, что тип a может быть любым типом, который является экземпляром класса Ord.

Вот самые часто используемые классы типов:

Eq

Класс Eq используется для типов, поддерживающих сравнение на равенство.
Методы класса:

(==) :: a -> a -> Bool infix 4
(/=) :: a -> a -> Bool

Его экземпляры должны реализовать по крайней мере один из этих методов.

Ord

Класс Ord используется для упорядоченных типов.
Методы класса:

(<) :: a -> a -> Bool
(<=) :: a -> a -> Bool
(>) :: a -> a -> Bool
(>=) :: a -> a -> Bool
compare :: a -> a -> Ordering -- Аналог оператора Comparison в C#.
max :: a -> a -> a
min :: a -> a -> a

Его экземпляры должны реализовать либо метод compare, либо (<=).

Enum

Класс Enum используется для упорядоченных последовательностей. Типы этого класса можно использовать в интервалах, например: ['a'..'z']. Не следует путать этот класс типов с перечислениями (Enum) в Java. Методы класса:

succ :: a -> a -- Возвращает следующий элемент.
pred :: a -> a -- Возвращает предыдущий элемент.
toEnum :: Int -> a -- Переводит значение типа Int в значение типа Enum.
fromEnum :: a -> Int -- Переводит значение типа Enum в значение типа Int.
enumFrom :: a -> [a]
enumFromThen :: a -> a -> [a]
enumFromTo :: a -> a -> [a]
enumFromThenTo :: a -> a -> a -> [a]

Его экземпляры должны реализовать минимум два метода: toEnum, fromEnum.

Bounded

Класс Bounded используется для типов у которых есть минальное и максимальное допустимые значения. Например тип Int имеет максимальное и минимальное значения.
Этот класс содержит два метода, которые обязательны для реализации: minBound и maxBound, возвращающие минимальное и максимальное значения соответственно.

Num

Класс Num используется для чисел.
Методы класса:

(+) :: a -> a -> a
(-) :: a -> a -> a
(*) :: a -> a -> a
negate :: a -> a -- Унарное вычитание.
abs :: a -> a
signum :: a -> a 
fromInteger :: Integer -> a

Его экземпляры должны реализовать следующие методы: (+), (*), abs, signum, fromIntegerи один из negate, (-).

Integral и Floating

Классы Integral и Floating являются обобщениями для целых чисел (Integral для Int и Integer) и чисел с плавающей точкой (Floating для Float и Dounble).

Экземпляры этих классов должны реализовать различные математические функции, которые можно найти в документации по ссылке. Их достаточно много, поэтому я не буду приводить их здесь.

Коментарии

Используйте Markdown

Thank you for comment!
Ваше сообщение будет доступно после проверки.