Jump to content
Main menu
Main menu
move to sidebar
hide
Navigation
Haskell
Wiki community
Recent changes
Random page
HaskellWiki
Search
Search
Create account
Log in
Personal tools
Create account
Log in
Pages for logged out editors
learn more
Contributions
Talk
Editing
Ru/IO Inside
(section)
Page
Discussion
English
Read
Edit
View history
Tools
Tools
move to sidebar
hide
Actions
Read
Edit
View history
General
What links here
Related changes
Special pages
Page information
Warning:
You are not logged in. Your IP address will be publicly visible if you make any edits. If you
log in
or
create an account
, your edits will be attributed to your username, along with other benefits.
Anti-spam check. Do
not
fill this in!
== Нотация '>>=' и 'do' == Все новички (включая меня :)) думают, что 'do' — это специальная конструкция для выполнения IO. Это не так — 'do' просто синтаксический сахар, делающий использование IO (и других монад) более удобным. 'do' в конечном счёте транслируется в код, передающий 'RealWorld', примерно так же, как мы это делали ранее вручную 'do' склеивает несколько IO-процедур в цепочку. (Точнее, 'do' транслируется в функциональный код, состоящий из '>>=', '>>' и лямбда-абстракций, а 'RealWorld' подставляется на следующем этапе. Другие монады 'RealWorld' не используют.) Каждая строка под 'do' называется командой (statement). IO-процедура — частный случай команды. 'do', применённое к только одной IO-процедуре, не делает ничего. Код: <haskell> main = do putStr "Hello!" </haskell> транслируется в: <haskell> main = putStr "Hello!" </haskell> Но признаком хорошего стиля считается использовать 'do' даже для одной IO-процедуры, так как это облегчает добавление новых процедур к цепочке в будущем. Посмотрим, как можно транслировать 'do' с несколькими IO-процедурами: <haskell> main = do putStr "What is your name?" putStr "How old are you?" putStr "Nice day!" </haskell> Здесь 'do' просто выполняет несколько IO-процедур последовательно. После трансляции мы увидим, что IO-процедуры объединены в цепочку с помощью операции '>>'. Она и другая операция '>>=' относятся к «связывающим» операциям ('>>=' так и называется: 'bind'): <haskell> main = (putStr "What is your name?") >> ( (putStr "How old are you?") >> (putStr "Nice day!") ) </haskell> '>>' просто передает мир от предыдущей IO-процедуры к последующей: <haskell> (>>) :: IO a -> IO b -> IO b (action1 >> action2) world0 = let (a, world1) = action1 world0 (b, world2) = action2 world1 in (b, world2) </haskell> Другой вариант того же определения: <haskell> action1 >> action2 = action where action world0 = let (a, world1) = action1 world0 (b, world2) = action2 world1 in (b, world2) </haskell> Подставьте определение '>>' в код, получившийся после трансляции 'do', чтобы убедиться, что код точно так же манипулирует миром, как мы манипулировали вручную. Есть другая разновидность команд 'do'. Они могут связывать переменную с помощью "<-" (аналогично тому, как связываются переменные в лямбда-абстракции): <haskell> main = do a <- readLn print a </haskell> Этот код транслируется в: <haskell> main = readLn >>= (\a -> print a) </haskell> В отличие от лямбда-абстракции, "<-" может связывать только одну переменную. Функция '>>' выбрасывает значение предыдущей IO-процедуры. '>>=', наоборот, позволяет использовать это значение в следующей IO-процедуре. Поэтому к её имени добавили знак равенства. Значение предыдущей IO-процедуры передаётся как аргумент последующей IO-процедуре: <haskell> (>>=) :: IO a -> (a -> IO b) -> IO b (action1 >>= action2) world0 = let (a, world1) = action1 world0 (b, world2) = action2 a world1 in (b, world2) </haskell> Расшифруем тип 'action2': "a -> IO b" = "a -> RealWorld -> (b, RealWorld)". Это значит, что 'action2' имеет два аргумента: аргумент типа 'a', который она использует некоторым неизвестным для нас образом, и аргумент типа 'RealWorld' для передачи управления. Все IO-процедуры имеют на один параметр больше, чем указано в их типе. Этот параметр спрятан в конструкторе типов 'IO'. Вы можете использовать '>>' и '>>=' наряду с 'do'. Иногда это упрощает код. В этом примере нет необходимости вводить переменную — результат 'readLn' можно прямо передать в 'print': <haskell> main = readLn >>= print </haskell> Наконец, конструкция: <haskell> do x <- action1 action2 </haskell> где 'action1' имеет тип "IO a" и 'action2' имеет тип "IO b", транслируется в: <haskell> action1 >>= (\x -> action2) </haskell> где второй аргумент функции '>>=' имеет тип "a -> IO b". Так транслируется '<-' — связанная им переменная может использоваться в дальнейших IO-процедурах, соединённых в одну большую IO-процедуру. Тип введённой переменной определяется типом 'action1': если 'action1' имеет тип "IO a", то 'x' имеет тип "a". Можно представить себе, что '<-' «раскрывает» IO-процедуру. '<-' не является функцией, это специальная синтаксическая конструкция, как и 'do'. Её смысл в том, как она транслируется. Следующий пример: <haskell> main = do putStr "What is your name?" a <- readLn putStr "How old are you?" b <- readLn print (a,b) </haskell> Код транслируется в: <haskell> main = putStr "What is your name?" >> readLn >>= \a -> putStr "How old are you?" >> readLn >>= \b -> print (a,b) </haskell> Скобки обущены; обе функции '>>' и '>>=' ассоциативны слева направо, но лямбда-абстракция всегда поглощает максимум кода справа от '->', насколько это возможно. Поэтому переменные 'a' и 'b' видимы во всём коде правее места, где 'a' и 'b' связываются. В качестве упражнения, расставьте скобки так, как их расставил бы парсер, и транслируйте код до 'RealWorld'. На этом можно закончить изучение 'do' и IO. Постойте! Я забыл про третью монадную функцию: 'return'. Она заключает два своих параметра в кортеж: <haskell> return :: a -> IO a return a world0 = (a, world0) </haskell> Попробуйте транслировать пример с 'return': <haskell> main = do a <- readLn return (a*2) </haskell> Программисты, раннее изучавшие императивные языки, ошибочно полагают, что 'return' в Хаскелле возвращает управление из IO-процедуры, опуская дальнейшие IO-процедуры. Но даже из её типа видно, что это не так. 'return' используется только для того, чтобы вернуть некоторое значение типа 'a' как результат IO-процедуры типа "IO a". Как правило, 'return' стоит в конце списка команд 'do'. Попробуйте транслировать следующий пример: <haskell> main = do a <- readLn when (a>=0) $ do return () print "a is negative" </haskell> чтобы убедиться, что 'print' будет выполнена при любых значениях 'a', а значение "()", которые вроде бы должен был вернуть 'main', просто потеряется. Потеряется потому, что код будет транслирован в функцию '>>', которая выбрасывает результат IO-процедуры. Для того, чтобы вернуть управление из середины IO-процедуры, используйте 'if': <haskell> main = do a <- readLn if (a>=0) then return () else print "a is negative" </haskell> Также Хаскелл позволяет вставить 'do' внутрь 'if': <haskell> main = do a <- readLn if (a>=0) then return () else do print "a is negative" ... </haskell> Так удобно выходить из середины длинного списка команд в 'do'. Последнее упражнение: напишите реализацию функции 'liftM', которая превращает обычную функцию в монадическую: <haskell> liftM :: (a -> b) -> (IO a -> IO b) </haskell> Если это для вас трудно, воспользуйтесь следующим функциональным определением: <haskell> liftM f action = do x <- action return (f x) </haskell> Не следует забывать, что всё, что является монадой, является и функтором. Поэтому на IO-процедурах можно использовать функцию 'fmap'. Вместо громоздкого <haskell> action >>= \x -> return (x+1) </haskell> или <haskell> do x <- action return (x+1) </haskell> можно написать: <haskell> fmap (+1) action </haskell> Также fmap = liftM, о которой мы говорили выше.
Summary:
Please note that all contributions to HaskellWiki are considered to be released under simple permissive license (see
HaskellWiki:Copyrights
for details). If you don't want your writing to be edited mercilessly and redistributed at will, then don't submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource.
DO NOT SUBMIT COPYRIGHTED WORK WITHOUT PERMISSION!
Cancel
Editing help
(opens in new window)
Toggle limited content width