After reading some very basic haskell now I know how to "chain" monadic actions using bind, like:
echo = getLine >>= putStrLn
(>>=)
operator is very handy in this fashion, but what if I want to chain monadic actions (or functors) that take multiple arguments?
Given that (>>=) :: m a -> (a -> m b) -> m b
it seems like (>>=)
can supply only one argument.
For example, writeFile
takes two arguments (a FilePath
and the contents). Suppose I have a monadic action that returns a FilePath
, and another action that returns a String
to write. How can I combine them with writeFile
, without using the do
-notation, but in a general way?
Is there any function with the type: m a -> m b -> (a -> b -> m c) -> m c
that can do this?
TL;DR:
writeFile <$> getFilename <*> getString >>= id :: IO ()
Monads are Applicative
Since ghc 7.10 every Monad (including IO
) is also an Applicative, but even before that, you could make an Applicative out of any Monad using the equivalent of
import Control.Applicative -- not needed for ghc >= 7.10
instance Applicative M where
pure x = return x
mf <*> mx = do
f <- mf
x <- mx
return (f x)
And of course IO
is a functor, but Control.Applicative
gives you <$>
which can be defined as f <$> mx = fmap f mx
.
Use Applicative to use functions that take as many arguments as you like
<$>
and <*>
let you use pure functions f
over arguments produced by an Applicative/Monadic computation, so if f :: String -> String -> Bool
and getFileName, getString :: IO String
then
f <$> getFileName <*> getString :: IO Bool
Similarly, if g :: String -> String -> String -> Int
, then
g <$> getString <*> getString <*> getString :: IO Int
From IO (IO ())
to IO ()
That means that
writeFile <$> getFilename <*> getString :: IO (IO ())
but you need something of type IO ()
, not IO (IO ())
, so we need to either use join :: Monad m => m (m a) -> m a
as in Xeo's comment, or we need a function to take the monadic result and run it, i.e. of type (IO ()) -> IO ()
to bind it with. That would be id
then, so we can either do
join $ writeFile <$> getFilename <*> getString :: IO ()
or
writeFile <$> getFilename <*> getString >>= id :: IO ()
It's much easier to use do
notation for this, rather than asking for a combinator
action1 :: MyMonad a
action2 :: MyMonad b
f :: a -> b -> MyMonad c
do
x <- action1
y <- action2
f x y
This typechecks:
import System.IO
filepath :: IO FilePath
filepath = undefined
someString :: IO String
someString = undefined
testfun = filepath >>= (\fp ->
someString >>= (\str ->
writeFile fp str ))
But I feel using do notation is more readable.