Como juntar duas mônadas Haskell IO
O seguinte programa Haskell (funcionando) produz um feitiço aleatório:
import System.Random
spells =
[ "Abracadabra!"
, "Hocus pocus!"
, "Simsalabim!"
]
main :: IO()
main = do
spell <- (spells !!) <$> randomRIO (0, length spells - 1)
putStrLn spell
No entanto, a variável spellé totalmente inútil. Ele armazena a sequência aleatória selecionada na lista de magias, mas é imediatamente passada para a putStrLnfunção e nunca é usada novamente. Tentei combinar as duas operações de IO em uma única linha, como esta:
main = putStrLn <$> (spells !!) <$> randomRIO (0, length spells - 1)
Mas recebi o seguinte erro:
• 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)
| ^^^^^^^^^^^^^^^^^^^^^^^^
Existe uma maneira de combinar as duas operações de E / S em uma única linha? Eu olhei para esta pergunta semelhante, mas não consegui entender a resposta.
Respostas
(>>=)é o operador de mônada "canônico", conforme dado na resposta de Robin Zigmond. No entanto, se você está tentando escrever código em um estilo de aplicativo, geralmente gosto de usar sua versão invertida (=<<),. Tem uma boa simetria com as funções em Functor e Applicative, e como elas se assemelham a uma chamada de função não monádica comum com apenas um operador extra interposto:
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?
Então sua expressão pode ser escrita
main = putStrLn =<< (spells !!) <$> randomRIO (0, length spells - 1)
Pessoalmente, prefiro usar uma composição de função mais comum e menos mapeamento contextual, então movo o (spells !!)para a esquerda do operador de ligação:
main = putStrLn . (spells !!) =<< randomRIO (0, length spells - 1)
Viu como fica bem assim? "Imprimir o feitiço no índice fornecido por randomRIO (0, length spells - 1)"?
doa notação é apenas um açúcar sintático para uso do operador bind ( >>=). Portanto, seu dobloco de 2 linhas pode ser reescrito:
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
No entanto, eu questionaria se há algum ganho de legibilidade, ou outro, fazendo isso aqui.