let (l, s’) = break (== ’n’) s
Мы сохраняем все символы до ’n’ от начала строки в переменной l. Затем мы рекурсивно вызываем
функцию lines на оставшейся части списка:
in
l : case s’ of
[]
-> []
(_:s’’) -> lines s’’
При этом мы пропускаем в s’ первый элемент, поскольку он содержит символ переноса каретки.
Посмотрим на ещё одну функцию для работы со строками.
words
:: String -> [String]
words s
=
case dropWhile Char. isSpace s of
”” -> []
s’ -> w : words s’’
where (w, s’’) = break Char. isSpace s’
Функция words делает тоже самое, что и lines, только теперь в качестве разделителя выступает пробел.
Функция dropWhile отбрасывает от начала списка все элементы, которые удовлетворяют предикату. В строке
case dropWhile Char. isSpace s of
Мы одновременно отбрасываем все первые пробелы и готовим значение для декомпозиции. Дальше мы
рассматриваем два возможных случая для строк.
”” -> []
s’ -> w : words s’’
where (w, s’’) = break Char. isSpace s’
Если строка пуста, то делать больше нечего. Если – нет, мы также как и в предыдущей функции приме-
няем функцию break для того, чтобы выделить все элементы кроме пробела, а затем рекурсивно вызываем
функцию words на оставшейся части списка.
4.6 Краткое содержание
В этой главе мы узнали очень много новых синтаксических конструкций для определения функций. Они
появлялись парами. Сведём их в таблицу:
Элемент
Декларативный стиль
Композиционный
Локальные переменные
where-выражения
let-выражения
Декомпозиция
Сопоставление с образцом
case-выражения
Условные выражения
Охранные выражения
if-выражения
Определение функций
Уравнения
лямбда-функции
Краткое содержание | 69
Особенности синтаксиса
Нам встретилась новая конструкция в сопоставлении с образцом:
beside :: Nat -> (Nat, Nat)
beside
Zero
= error ”undefined”
beside
x@(Succ y) = (y, Succ x)
Она позволяет проводить декомпозицию и давать имя всему значению одновременно. Такие выражения
x(…)@ в англоязычной литературе принято называть as-patterns.
4.7 Упражнения
• В этой главе нам встретилось много полезных стандартных функций, потренируйтесь с ними в интер-
претаторе. Вызывайте их с различными значениями, экспериментируйте.
• Попробуйте определить функции из предыдущих глав в чисто композиционном стиле.
• Посмотрите на те функции, которые мы прошли и попробуйте переписать их определения шиворот
на выворот. Если вы видите, что элемент написан композиционном стиле перепишите его в деклара-
тивном и наоборот. Получившиеся функции могут показаться монстрами, но это упражнение может
помочь вам в закреплении новых конструкций и почувствовать сильные и слабые стороны того или
иного стиля.
• Определите модуль, который будет вычислять площади простых фигур, треугольника, окружности,
прямоугольника, трапеции. Помните, что фигуры могут задаваться различными способами.
• Поток это бесконечный список, или список, у которого нет конструктора пустого списка:
data Stream a = a :& Stream a
Так например мы можем составить поток из всех чисел Пеано:
nats :: Nat -> Stream Nat
nats a = a :& nats (Succ a)
Или поток, который содержит один и тот же элемент:
constStream :: a -> Stream a
constStream a = a :& constStream a
Напишите модуль для потоков. В первую очередь нам понадобятся функции выделения частей потока,
поскольку мы не сможем распечатать поток целиком (ведь он бесконечный):
— Первый элемент потока
head :: Stream a -> a
— Хвост потока, всё кроме первого элемента
tail :: Stream a -> Stream a
— n-тый элемент потока
(!! ) :: Stream a -> Int -> a
— Берёт из потока несколько первых элементов:
take :: Int -> Stream a -> [a]
Имена этих функций будут совпадать с именами функций для списков чтобы избежать коллизий имён
мы воспользуемся квалифицированным импортом функций. Делается это так:
import qualified Prelude as P( определения )
Слова qualified и as – ключевые. Теперь для использования функций из модуля Prelude мы будем писать
P.имяФункции. Такие имена называются квалифицированными. Для того чтобы пользоваться квалифициро-
ванными именами только для тех функций, для которых возможна коллизия имён можно поступить так:
70 | Глава 4: Декларативный и композиционный стиль
import qualified Prelude as P
import Prelude
Компилятор разберётся, какую функцию мы имеем в виду.
Для удобства тестирования можно определить такую функцию печати потоков:
instance Show a => Show (Stream a) where
show xs =
showInfinity (show (take 5 xs))
where showInfinity x = P. init x
P.++ ”…”
Функция P. init выделяет все элементы списка кроме последнего. В данном случае она откусит от строки
закрывающуюся скобку. После этого мы добавляем троеточие, как символ бесконечности списка.
Функции преобразования потоков:
— Преобразование потока
map :: (a -> b) -> Stream a -> Stream b
— Фильтрация потока
filter :: (a -> Bool) -> Stream a -> Stream a
— zip-ы для потоков:
zip :: Stream a -> Stream b -> Stream (a, b)
zipWith :: (a -> b -> c) -> Stream a -> Stream b -> Stream c
Функция генерации потока:
iterate :: (a -> a) -> a -> Stream a
Эта функция принимает два аргумента: функцию следующего элемента потока и значение первого эле-