ifThenElse :: Bool -> a -> a -> a
ifThenElse True
t
_ = t
ifThenElse False
_
e = e
Эта функция первым аргументом принимает значение типа Bool, а вторым и третьим – альтернативы
некоторого типа a. Если первый аргумент – True, возвращается второй аргумент, а если – False, то третий.
Интересно, что в Haskell ничего не происходит, мир Haskell-значений стоит на месте. Мы просто даём
имена разным комбинациям слов. Определяем новые термины. Потом на этих терминах определяем новые
термины, и так далее. Кажется, если ничего не меняется, то зачем язык? И что мы собираемся программиро-
вать без вычислений?
Значения | 17
Разгадка кроется в функциях not, and и or. До того как мы их определили, у нас было четыре имени, но
после их определения имён стало бесконечное множество. Три синонима пополнили наш язык бесконечным
набором комбинаций. В этом суть. Мы определяем базовые элементы и способы составления новых, потом
мы просим ”вычислить’ комбинацию из них. Мы не определяли явно, чему равна комбинация not (and true
False), это сделал за нас вычислитель Haskell1.
Вычислить стоит в кавычках, потому что на самом деле вычислений нет, есть замена синонимов на ком-
бинации простейших элементов.
Ещё один пример, положим у нас есть тип:
data Status = Work | Rest
Он определяет, что делать в данный день: работать (Work) или отдыхать (Rest). У разных рабочих разный
график. Например, есть функции:
jonny :: Week -> Status
jonny x = ...
colin :: Week -> Status
colin x = ...
Конкретное определение сейчас не важно, важно, что они определяют зависимость статуса (Status) от
дня недели (Week) для работников Джонни (jonny) и Колина (colin).
Также у нас есть полезная функция:
calendar :: Date -> Week
calendar x = ...
Она определяет по дате день недели. И теперь, зная лишь эти функции, мы можем спросить у вычислителя
будет ли у Джонни выходной 8 августа 3043 года:
jonny (calendar (Date (Year 3043) August (Day 8)))
=> jonny Saturday
=> Rest
Интересно, у нас опять всего лишь два значения, но, дав такое большое имя одному из значений, мы
смогли получить полезную нам информацию, ничего не вычисляя.
1.4 Классы типов
Если типы и значения – привычные понятия, которые можно найти в том или ином виде в любом языке
программирования, то термин класс типов встречается не часто. У него нет аналогов и в обычном языке,
поэтому я сначала постараюсь объяснить его смысл на примере.
В типизированном языке у каждой функции есть тип, но бывают функции, которые могут быть опреде-
лены на аргументах разных типов; по сути, они описывают схожие понятия, но определены для значений
разных типов. Например, функция сравнения на равенство, говорящая о том, что два значения одного типа
a равны, имеет тип a -> a -> Bool, или функция печати выражения имеет тип a -> String, но что такое
a в этих типах? Тип a является любым типом, для которого сравнение на равенство или печать (преобразо-
вание в строку) имеют смысл. Это понятие как раз и кодируется в классах типов. Классы типов (type class)
позволяют определять функции с одинаковым именем для разных типов.
У классов типов есть имена. Также как и имена классов, они начинаются с большой буквы. Например,
класс сравнений на равенство называется Eq (от англ. equals – равняется), а класс печати выражений имеет
имя Show (от англ. show – показывать). Посмотрим на их определения:
Класс Eq:
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
Класс Show:
1Было бы точнее называть вычислитель редуктором, поскольку мы проводим редукции, или замену эквивалентных значений, но
закрепилось это название. К тому же, редуктор также обозначает прибор.
18 | Глава 1: Основы
class Show a where
show :: a -> String
За ключевым словом class следует имя класса, тип-параметр и ещё одно ключевое слово where. Далее с
отступами пишутся имена определённых в классе значений. Значения класса называются методами.
Мы определяем лишь типы методов, конкретная реализация будет зависеть от типа a. Методы определя-
ются в экземплярах классов типов, мы скоро к ним перейдём.