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
letx =c +b
y =x +c
return y
Посмотрим как do-нотация переводится в выражение, составленное с помощью методов класса Monad:
do
a <-ma
=>
ma >>=(\a ->exp)
exp
do
exp1
=>
exp1 >>exp2
exp2
do
letx =fx
=>
letx =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 Расширения
Расширение появляется в ответ на проблему, с которой трудно или невозможно справится в рамках стан-
дарта Haskell. Мы рассмотрим несколько наиболее часто используемых расширений. Расширения подключа-
ются с помощью специального комментария. Он помещается в начале модуля. Расширение действует только
в текущем модуле.
{-# LANGUAGE
ExtentionName1, ExtentionName2, ExtentionName3 #-}
Обратите внимание на символ решётка, обрамляющие комментарии. Слово LANGUAGEговорит компи-
лятору о том, что мы хотим воспользоваться расширениями с именами ExtentionName1, ExtentionName2,
ExtentionName3. Такой комментарий называется прагмой (pragma). Часто компилятор ghc в случае ошибки
предлагает нам подключить расширение, в котором ошибка уже не будет ошибкой, а возможностью языка.
Он говорит возможно вы имели в виду расширение XXX. Например попробуйте загрузить в интерпретатор
модуль:
module Test where
class Multia b where
В этом случае мы увидим ошибку:
Prelude> :l Test
[1 of1] Compiling Test
( Test.hs, interpreted )
Test.hs :3 :0 :
Toomany parameters for class‘ Multi’
( Use -XMultiParamTypeClassesto allow multi -parameter classes)
Inthe classdeclaration for ‘ Multi’
Failed, modules loaded :none .
Компилятор сообщает нам о том, что у нас слишком много параметров в классе Multi. В рамках стандар-
та Haskell можно создавать лишь классы с одним параметром. Но за сообщением мы видим подсказку, если
мы воспользуемся расширением -XMultiParamTypeClasses, то всё будет хорошо. В этом сообщении имя рас-
ширения закодировано в виде флага. Мы можем запустить ghc или ghci с этим флагом и тогда расширение
будет активировано, и модуль загрузится. Попробуем:
Читать дальше