17
votes

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?

3
First, putStrLn is not a monad. It's a function that returns a monad (the IO action). Second, >>= (read 'bind') doesn't chain monads directly. It allows you to call functions on monadic values. - Xeo
Oh, and btw: join $ writeFile <$> getFilepath <*> getSomeString. Figuring out what this does is left as an exercise for the reader. - Xeo
@Xeo: An IO action is not a monad either. The IO type (and associated instance) is the monad here. - Tom Ellis

3 Answers

17
votes

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 ()
7
votes

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
3
votes

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.