[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 =<<
liftA2 (++) (readFile file1) (readFile file2)
Пуд сахара | 253
17.2 Расширения
Расширение появляется в ответ на проблему, с которой трудно или невозможно справится в рамках стан-