Выбрать главу

на соответствующую логическую операцию, а пустой список заменяется на значение, которое не влияет на

результат выполнения данной логической операции. Имеется ввиду, что функции (&& True) и (|| False)

дают тот же результат, что и функция id x = x. Функция (++) объединяет два списка, а функция concat

выполняет ту же операцию, но на списке списков.

Функция zip принимает два списка и смешивает их в список пар. Как только один из списков оборвётся

оборвётся и список-результат. Эта функция является частным случаем более общей функции zipWith, кото-

рая принимает функцию двух аргументов и два списка и составляет новый список попарных применений.

-- zip-ы

zip :: [a] -> [b] -> [(a, b)]

zip = zipWith (,)

zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]

zipWith z (a:as) (b:bs) =

z a b : zipWith z as bs

zipWith _ _ _

=

[]

Посмотрим как работают эти функции в интерпретаторе:

Prelude> zip [1,2,3] ”hello”

[(1,’h’),(2,’e’),(3,’l’)]

Prelude> zipWith (+) [1,2,3] [3,2,1]

[4,4,4]

Prelude> zipWith (*) [1,2,3] [5,4,3,2,1]

[5,8,9]

Отметим, что в Prelude также определена обратная функция unzip:

68 | Глава 4: Декларативный и композиционный стиль

unzip

:: [(a,b)] -> ([a], [b])

Она берёт список пар и разбивает его на два списка.

Пока по этим определениям кажется, что композиционный стиль совсем нигде не применяется. Он встре-

тился нам лишь в функции break. Но давайте посмотрим и на функции с композиционным стилем:

lines

:: String -> [String]

lines ””

=

[]

lines s

=

let (l, s’) = break (== ’\n’) s

in

l : case s’ of

[]

-> []

(_:s’’) -> lines s’’

Функция line разбивает строку на список строк. Эти строки были разделены в исходной строке символом

переноса ’\n’.

Функция break принимает предикат и список и возвращает два списка. В первом все элементы от начала

списка, которые не удовлетворяют предикату, а во втором все остальные. Наш предикат (== ’\n’) выделяет

все символы кроме переноса каретки. В строке

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”