из модуля EmptyEmpty:
module Empty where
import EmptyEmpty
Когда у нас будет много модулей мы можем разместить их по директориям. Создадим в одной дирек-
тории с модулем Empty директорию Sub, а в неё поместим копию модуля Empty. Существует одна тонкость:
поскольку модуль находится в поддиректории, для того чтобы он стал виден из текущей директории, необ-
ходимо дописать через точку имя директории в которой он находится:
module Sub.Empty where
Теперь мы можем загрузить этот модуль из исходного:
module Empty where
import EmptyEmpty
import Sub.Empty
Обратите внимание на то, что мы приписываем к модулю в поддиректории Sub имя поддиректории. Если
бы он был заложен в ещё одной директории, то мы написали бы через точку имя и этой поддиректории:
module Empty where
import Sub1.Sub2.Sub3.Sub4.Empty
26 | Глава 2: Первая программа
2.3 Логические значения
Пустой модуль это хорошо, но слишком скучно. Давайте перепишем объявленные в этой главе опреде-
ления в модуль, загрузим его в интерпретатор и понабираем значения.
Начнём с логических операций. Давайте не будем переопределять Bool, Show и Eq, а просто возьмём их
из Prelude:
module Logic where
import Prelude(Bool(.. ), Show(.. ), Eq(.. ))
Две точки в скобках означают “все конструкторы” (в случае типа) и “все методы” (в случае класса типа).
Строчку
import Prelude(Bool(.. ), Show(.. ), Eq(.. ))
Следует читать так: Импортируй из модуля Prelude тип Bool и все его конструкторы и классы Show и
Eq со всеми их методами. Если бы мы захотели импортировать только конструктор True, мы бы написали
Bool(True), а если бы мы захотели импортировать лишь имя типа, мы бы написали просто Bool без скобок.
Сначала выпишем в модуль наши синонимы:
module Logic where
import Prelude(Bool(.. ), Show(.. ), Eq(.. ))
true :: Bool
true = True
false :: Bool
false = False
not :: Bool -> Bool
not True
= False
not False = True
and :: Bool -> Bool -> Bool
and False
_
= False
and True
x
= x
or
:: Bool -> Bool -> Bool
or True
_ = True
or False
x = x
xor :: Bool -> Bool -> Bool
xor a b = or (and (not a) b) (and a (not b))
ifThenElse :: Bool -> a -> a -> a
ifThenElse True
t
_ = t
ifThenElse False
_
e = e
Теперь сохраним модуль и загрузим его в интерпретатор. Для наглядности мы установим флаг +t, при
этом будет возвращено не только значение, но и его тип. Понабираем разные комбинации значений:
*Logic> :l Logic
[1 of 1] Compiling Logic
( Logic. hs, interpreted )
Ok, modules loaded: Logic.
*Logic> :set +t
*Logic> not (and true False)
True
it :: Bool
*Logic> or (and true true) (or False False)
True
it :: Bool
*Logic> xor (not True) (False)
False
it :: Bool
*Logic> ifThenElse (or true false) True False
True
it :: Bool
Логические значения | 27
Разумеется в Haskell уже определены логические операции, здесь мы просто тренировались. Они называ-
ются not, (&& ), ||. Операция xor это то же самое, что и (/=). Для Bool определён экземпляр класса Eq. Также
в Haskell есть конструкция ветвления она пишется так:
x = if cond then t else e
Слова if, then и else – ключевые. cond имеет тип Bool, а t и e одинаковый тип.
В коде программы обычно пишут так:
x = if a > 3
then ”Hello”
else (if a < 0
then ”Hello”
else ”Bye”)
Отступы обязательны.
Давайте загрузим в интерпретатор модуль Prelude и наберём те же выражения стандартными функция-
ми:
*Logic> :m Prelude
Prelude> not (True && False)
True
it :: Bool
Prelude> (True && True) || (False || False)
True
it :: Bool
Prelude> not True /= False
False
it :: Bool
Prelude> if (True || False) then True else False
True
it :: Bool
Бинарные операции с символьными именами пишутся в инфиксной форме, то есть между аргументами
как в a && b или a + b. Значение с буквенным именем также можно писать в инфиксной форме, для этого
оно заключается в апострофы, например a ‘and‘ b или a ‘plus‘ b. Апострофы обычно находятся на одной
кнопке с буквой “ё”. Также символьные функции можно применять в префиксной форме, заключив их в
скобки, например (&& ) a b и (+) a b. Попробуем в интерпретаторе:
Prelude> True && False
False
it :: Integer
Prelude> (&& ) True False
False
it :: Bool
Prelude> let and a b = a && b
and :: Bool -> Bool -> Bool
Prelude> and True False
False
it :: Bool
Prelude> True ‘and‘ False
False
it :: Bool
Обратите внимание на строчку let and a b = a && b. В ней мы определили синоним в интерпретаторе.
Сначала мы пишем ключевое слово let затем обычное определение синонима, как в программе. Это простое
однострочное определение, но мы можем набирать в интерпретаторе и более сложные. Мы можем написать
несколько строчек в одной, разделив их точкой с запятой:
Prelude> let not2 True = False; not2 False = True
Мы можем записать это определение более наглядно, совсем как в редакторе, если воспользуемся много-
строчным вводом. Для этого просто наберите команду :{. Для выхода воспользуйтесь командой :}. Отметим,
что точкой с запятой можно пользоваться и в обычном коде. Например в том случае если у нас много кратких
определений и мы хотим записать их покомпактней, мы можем сделать это так:
a1 = 1;
a2 = 2;
a3 = 3
a4 = 4;
a5 = 5;
a6 = 6
28 | Глава 2: Первая программа
2.4 Класс Show. Строки и символы
Мы набираем в интерпретаторе какое-нибудь сложное выражение, или составной синоним, интерпрета-
тор проводит редукцию и выводит ответ на экран. Откуда интерпретатор знает как отображать значения
типа Bool? Внутри интерпретатора вызывается метод класса Show, который переводит значение в строку. И
затем мы видим на экране ответ.
Для типа Bool экземпляр класса Show уже определён, поэтому интерпретатор знает как его отображать.
Обратите внимание на эту особенность языка, вид значения определяется пользователем, в экземпляре
класса Show. Из соображений наглядности вид значения может сильно отличаться от его внутреннего пред-
ставления.
В этом разделе мы рассмотрим несколько примеров с классом Show, но перед этим мы поговорим о стро-
ках и символах в языке Haskell.
Строки и символы
Посмотрим в интерпретаторе на определение строк (тип String), для этого мы воспользуемся командой
:i (сокращение от :info):
Prelude> :i String
type String = [Char]
— Defined in ‘GHC.Base’
Интерпретатор показал определение типа и в комментариях указал в каком модуле тип определён. В
этом определении мы видим новое ключевое слово type. До этого для определения типов нам встречалось
лишь слово data. Ключевое слово type определяет синоним типа. При этом мы не вводим новый тип, мы
лишь определяем для него псевдоним. String является синонимом для списка значений типа Char. Тип
Char представляет символы. Итак строка – это список символов. В Haskell символы пишутся в ординарных
кавычках, а строки в двойных:
Prelude> [’H’,’e’,’l’,’l’,’o’]
”Hello”
it :: [Char]
Prelude> ”Hello”
”Hello”
it :: [Char]
Prelude> ’+’
’+’
it :: Char
Для обозначения перехода на новую строку используется специальный символ n. Если строка слишком
длинная и не помещается на одной строке, то её можно перенести так:
str = ”My long long long long
long long string”
Перенос осуществляется с помощью комбинации следующих друг за другом обратных слэшей.
Нам понадобится функция конкатенации списков (++), она определена в Prelude, с её помощью мы будем





