В этой записи мы фильтруем список 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
<-
mxxs <-
sequence mxsreturn (x:
xs)Во втором уравнении сначала мы говорим вычислителю словом do
о том, что выражения записаны в миремонады m. Запись с перевёрнутой стрелкой x <-
mx означает, что мы далее в do-блоке можем пользоватьсязначением x так словно оно имеет тип просто a, но не m a. Смотрите в этом определении мы сначала извле-
каем первый элемент списка, затем извлекаем хвост списка, приведённый к типу m [a], и в самом конце мы
соединяем голову и хвост и в самом конце оборачиваем результат в специальное значение.
Например мы можем построить функцию, которая дважды читает строку со стандартного ввода и затем
возвращает объединение двух строк:
252 | Глава 17: Дополнительные возможности
getLine2 :: IO String
getLine2 = do
a <-
getLineb <-
getLinereturn (a ++
b)В do
-нотации можно вводить локальные переменные с помощью слова let:t = do
b <-
f ac <-
g blet
x = c + by =
x + creturn y
Посмотрим как do
-нотация переводится в выражение, составленное с помощью методов класса Monad:do
a <-
ma=>
ma >>=
(\a -> exp)exp
do
exp1
=>
exp1 >>
exp2exp2
do
let
x = fx=>
let
x = fxy =
fyy =
fyexp
in
exp
Переведём с помощью этих правил определение для второго уравнения из функции sequence
sequence (mx:
mxs) = dox
<-
mxmx >>=
(\x -> doxs
<-
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 <-
mxy <-
myreturn (op x y)
Можно записать гораздо короче:
f =
liftA2 opНапример напишем функцию, которая объединяет два файла в один:
appendFiles :: FilePath -> FilePath -> FilePath -> IO
()С помощью do
-нотации:appendFiles file1 file2 resFile = do
a <-
readFile file1b <-
readFile file2writeFile resFile (a ++
b)А теперь с помощью класса Applicative
:appendFiles file1 file2 resFile =
writeFile resFile =<<liftA2 (++
) (readFile file1) (readFile file2)Пуд сахара | 253
17.2 Расширения
Расширение появляется в ответ на проблему, с которой трудно или невозможно справится в рамках стан-
дарта Haskell. Мы рассмотрим несколько наиболее часто используемых расширений. Расширения подключа-
ются с помощью специального комментария. Он помещается в начале модуля. Расширение действует только
в текущем модуле.
{-# LANGUAGE
ExtentionName1, ExtentionName2, ExtentionName3 #-}
Обратите внимание на символ решётка, обрамляющие комментарии. Слово LANGUAGE
говорит компи-лятору о том, что мы хотим воспользоваться расширениями с именами ExtentionName1
, ExtentionName2,ExtentionName3
. Такой комментарий называетсяпредлагает нам подключить расширение, в котором ошибка уже не будет ошибкой, а возможностью языка.
Он говорит возможно вы имели в виду расширение XXX
. Например попробуйте загрузить в интерпретатормодуль:
module Test where
class Multi
a b whereВ этом случае мы увидим ошибку:
Prelude> :
l Test[1 of
1] Compiling Test( Test.
hs, interpreted )Test.
hs:3:0:Too
many parameters for class ‘Multi’