haskell-notes

сать формулу для вычисления площади треугольника:

square a b c =

(p -> sqrt (p * (p a) * (p b) * (p c)))

((a + b + c) / 2)

Смотрите мы определили функцию, которая принимает параметром полупериметр p и передали в неё

значение ((a + b + c) / 2). Если в нашей функции несколько локальных переменных, то мы можем

составить лямбда-функцию от нескольких переменных и подставить в неё нужные значения.

4.5 Какой стиль лучше?

Основной критерий выбора заключается в том, сделает ли этот элемент код более ясным. Наглядность

кода станет залогом успешной поддержки. Его будет легче понять и улучшить при необходимости.

Далее мы рассмотрим несколько примеров определений из Prelude и подумаем, почему был выбран тот

или иной стиль. Начнём с класса Ord и посмотрим на определения по умолчанию:

— Тип упорядочивания

data

Ordering

=

LT | EQ | GT

deriving (Eq, Ord, Enum, Read, Show, Bounded)

class

(Eq a) => Ord a

where

compare

:: a -> a -> Ordering

(< ), (<=), (>=), (> ) :: a -> a -> Bool

max, min

:: a -> a -> a

— Минимальное полное определение:

(<=) или compare

— Использование compare может оказаться более

— эффективным для сложных типов.

compare x y

| x == y

=

EQ

| x <= y

=

LT

| otherwise =

GT

x <= y

=

compare x y /= GT

x <

y

=

compare x y == LT

x >= y

=

compare x y /= LT

x >

y

=

compare x y == GT

max x y

| x <= y

=

y

| otherwise =

x

min x y

| x <= y

=

x

| otherwise =

y

Все функции определены в декларативном стиле. Тип Ordering кодирует результат операции сравнения.

Два числа могут быть либо равны (значение EQ), либо первое меньше второго (значение LT), либо первое

больше второго (значение GT).

Обратите внимание на функцию compare. Мы не пишем дословное определение значений типа Ordering:

compare x y

| x == y

=

EQ

| x <

y

=

LT

| x >

y

=

GT

Какой стиль лучше? | 67

В этом случае функция compare была бы определена через две других функции класса Ord, а именно

больше > и меньше < . Мы же хотим минимизировать число функций в этом определении. Поэтому вместо

этого определения мы полагаемся на очерёдность обхода альтернатив в охранном выражении.

Если первый случай не прошёл, то во втором случае нет разницы между функциями < и <=. А если не

прошёл и этот случай, то остаётся только вернуть значение GT. Так мы определили функцию compare через

одну функцию класса Ord.

Теперь посмотрим на несколько полезных функций для списков. Посмотрим на три основные функции

для списков, одна из них возможно вам уже порядком поднадоела:

— Преобразование списка

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

map f []

= []

map f (x:xs) = f x : map f xs

— Фильтрация списка

filter :: (a -> Bool) -> [a] -> [a]

filter p []

= []

filter p (x:xs) | p x

= x : filter p xs

| otherwise = filter p xs

— Свёртка списка

foldr

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

foldr f z []

=

z

foldr f z (x:xs) =

f x (foldr f z xs)

Приведём несколько примеров для функции foldr:

and, or :: [Bool] -> Bool

and = foldr (&& ) True

or

= foldr (||) False

(++) :: [a] -> [a] -> [a]

[]

++ ys = ys

(x:xs) ++ ys = x : (xs ++ ys)

concat :: [[a]] -> [a]

concat = foldr (++) []

Функции and и or выполняют логические операции на списках. Так каждый конструктор (:) заменяется

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

результат выполнения данной логической операции. Имеется ввиду, что функции (&& 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’) выделяет

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

Страницы: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162