haskell-notes

Для класса Enum определён специальный синтаксис составления последовательностей перечисляемых

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

Prelude> [0 .. 10]

[0,1,2,3,4,5,6,7,8,9,10]

А так мы можем составить бесконечную последовательность положительных чисел:

Prelude> take 20 $ [0 .. ]

[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]

Мы можем составлять последовательности с определённым шагом. Так можно выделить все чётные по-

ложительные числа:

Prelude> take 20 $ [0, 2 .. ]

[0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38]

А так мы можем составить убывающую последовательность чисел:

Prelude> [10, 9 .. 0]

[10,9,8,7,6,5,4,3,2,1,0]

Что интересно в списке могут находиться не только числа, а любые значения из класса Enum. Например

определим тип:

data Day

= Monday | Tuesday | Wednesday | Thursday

| Friday | Saturday | Sunday

deriving (Show, Enum)

Теперь мы можем написать:

*Week> [Friday .. Sunday]

[Friday, Saturday, Sunday]

*Week> [ Monday .. ]

[Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday]

Также шаг последовательности может быть и дробным:

*Week> [0, 0.5 .. 4]

[0.0,0.5,1.0,1.5,2.0,2.5,3.0,3.5,4.0]

| 251

Генераторы списков

Генераторы списков (list comprehensions) объединяют в себе функции преобразования и фильтрации спис-

ков. Они записываются так:

[ f x | x <- list, p x]

В этой записи мы фильтруем список list предикатом p и преобразуем результат функцией f. Например

возведём в квадрат все чётные элементы списка:

Prelude> [x*x | x <- [1 .. 10], even x]

[4,16,36,64,100]

Предикатов может быть несколько, так например мы можем оставить лишь положительные чётные числа:

Prelude> [x | x <- [10 .. 10], even x, x >= 0]

[0,2,4,6,8,10]

Также элементы могут браться из нескольких списков, посмотрим на все возможные комбинации букв из

пары слов:

Prelude> [ [x,y] | x <- ”Hello”, y <- ”World”]

[”HW”,”Ho”,”Hr”,”Hl”,”Hd”,”eW”,”eo”,”er”,”el”,

”ed”,”lW”,”lo”,”lr”,”ll”,”ld”,”lW”,”lo”,”lr”,

”ll”,”ld”,”oW”,”oo”,”or”,”ol”,”od”]

Сахар для монад, do-нотация

Монады используются столь часто, что для них придумали специальный синтаксис, который облегчает

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

специальные функции вида

a -> m b

Если бы эти функции выглядели как обычные функции:

a -> b

их можно было свободно комбинировать с другими функциями. А так нам постоянно приходится поль-

зоваться методами класса Monad. Очень часто функции с побочными эффектами имеют вид:

a1 -> a2 -> a3 -> … -> an -> m b

А теперь представьте, что вам нужно подставить специальное значение третьим аргументом такой функ-

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

специальное окружение do, в котором специальные функции комбинируются так словно они являются обыч-

ными. Для этого используется обратная стрелка. Посмотрим как определяется функция sequence в окруже-

нии do:

sequence :: [m a] -> m [a]

sequence []

= return []

sequence (mx:mxs)

= do

x

<- mx

xs <- sequence mxs

return (x:xs)

Во втором уравнении сначала мы говорим вычислителю словом do о том, что выражения записаны в мире

монады m. Запись с перевёрнутой стрелкой x <- mx означает, что мы далее в do-блоке можем пользоваться

значением x так словно оно имеет тип просто a, но не m a. Смотрите в этом определении мы сначала извле-

каем первый элемент списка, затем извлекаем хвост списка, приведённый к типу m [a], и в самом конце мы

соединяем голову и хвост и в самом конце оборачиваем результат в специальное значение.

Например мы можем построить функцию, которая дважды читает строку со стандартного ввода и затем

возвращает объединение двух строк:

252 | Глава 17: Дополнительные возможности

getLine2 :: IO String

getLine2 = do

a <- getLine

b <- getLine

return (a ++ b)

В do-нотации можно вводить локальные переменные с помощью слова let:

t = do

b <- f a

c <- g b

let x = c + b

y = x + c

return y

Посмотрим как do-нотация переводится в выражение, составленное с помощью методов класса Monad:

do

a <- ma

=>

ma >>= (a -> exp)

exp

do

exp1

=>

exp1 >> exp2

exp2

do

let x = fx

=>

let x = fx

y = fy

y = fy

exp

in

exp

Переведём с помощью этих правил определение для второго уравнения из функции sequence

sequence (mx:mxs) = do

x

<- mx

mx >>= (x -> do

xs

<- sequence mxs

=>

xs <- sequence mxs

=>

return (x:xs)

return (x:xs))

=>

mx >>= (x -> sequence mxs >>= (xs -> return (x:xs)))

do или Applicative?

С появлением класса Applicative во многих случаях do-нотация теряет свою ценность. Так например

любой do-блок вида:

f mx my = do

x <- mx

y <- my

return (op x y)

Можно записать гораздо короче:

f = liftA2 op

Например напишем функцию, которая объединяет два файла в один:

appendFiles :: FilePath -> FilePath -> FilePath -> IO ()

С помощью do-нотации:

appendFiles file1 file2 resFile = do

a <- readFile file1

b <- readFile file2

writeFile resFile (a ++ b)

А теперь с помощью класса Applicative:

appendFiles file1 file2 resFile = writeFile resFile =<<

Страницы: 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