Как соединить две монады Haskell IO
Следующая (рабочая) программа на Haskell выводит случайное заклинание:
import System.Random
spells =
[ "Abracadabra!"
, "Hocus pocus!"
, "Simsalabim!"
]
main :: IO()
main = do
spell <- (spells !!) <$> randomRIO (0, length spells - 1)
putStrLn spell
Однако переменная spellсовершенно бесполезна. Он сохраняет случайную строку, выбранную из списка заклинаний, но затем немедленно передается в putStrLnфункцию и больше никогда не используется. Я попытался объединить две операции ввода-вывода в одну строку следующим образом:
main = putStrLn <$> (spells !!) <$> randomRIO (0, length spells - 1)
Но я получил следующую ошибку:
• Couldn't match type ‘IO ()’ with ‘()’
Expected type: Int -> ()
Actual type: Int -> IO ()
• In the first argument of ‘(<$>)’, namely
‘putStrLn <$> (spells !!)’ In the expression: putStrLn <$> (spells !!) <$> randomRIO (0, length spells - 1) In an equation for ‘main’: main = putStrLn <$> (spells !!) <$> randomRIO (0, length spells - 1) | 160 | main = putStrLn <$> (spells !!) <$> randomRIO (0, length spells - 1)
| ^^^^^^^^^^^^^^^^^^^^^^^^
Есть ли способ объединить две операции ввода-вывода в одну строку? Я посмотрел на этот похожий вопрос, но не смог понять ответа.
Ответы
(>>=)является «каноническим» оператором монады, как указано в ответе Робина Зигмонда. Однако, если вы пытаетесь код записи в аппликативном-как стиль, я часто люблю использовать свою перевернутую версию, (=<<). Он имеет приятную симметрию с функциями в Functor и Applicative и тем, как они напоминают вызов обычной немонадической функции с добавлением только одного дополнительного оператора:
f x -- one-argument function call
f <$> fx -- fmapping that function into a functor g x y -- two-argument function call g <$> ax <*> ay -- applied over two applicatives
f =<< mx -- binding a function with a monadic value
mx >>= f -- looks backwards, doesn't it?
Так что ваше выражение может быть написано
main = putStrLn =<< (spells !!) <$> randomRIO (0, length spells - 1)
Лично я бы предпочел использовать более обычную композицию функций и менее контекстное сопоставление, поэтому я бы переместил (spells !!)левую часть оператора привязки:
main = putStrLn . (spells !!) =<< randomRIO (0, length spells - 1)
Видите, как это хорошо читается в таком порядке? "Распечатать заклинание по индексу, заданному randomRIO (0, length spells - 1)"?
doнотация - это просто синтаксический сахар для использования оператора связывания ( >>=). Итак, ваш двухстрочный doблок можно переписать:
main = do
spells <- (spells !!) <$> randomRIO (0, length spells - 1) putStrLn spells -- rewrite to bind notation main = ((spells !!) <$> randomRIO (0, length spells - 1)) >>= \spells ->
putStrLn spells
-- simplify the lambda
main = ((spells !!) <$> randomRIO (0, length spells - 1)) >>= putStrLn
Однако я бы спросил, есть ли какая-либо читаемость или другая выгода от этого.