Combining monads

Glynn Clements glynn.clements@virgin.net
Sat, 12 Apr 2003 15:26:03 +0100


Mark T.B. Carroll wrote:

> Hello! I am trying and failing to use one monad in another. A tiny example
> is enclosed, where I try to use IO to help along a State-ful computation.
> Is the real mistake in do_next? Is there a minimal rewrite that will get
> this working that I can use as a template for other things? Or, is there a
> good tutorial somewhere on this type of thing? Any pointers are much
> appreciated!

Combining monads isn't straightforward. There is a paper on the topic,
at:

http://cm.bell-labs.com/cm/cs/who/wadler/topics/monads.html#combining-monads

However, you can't really combine the IO monad in the way that the
above paper describes.

The most direct way to fix your program is to use an IORef instead of
a state monad, e.g.

> import IORef

> update_best_with :: IORef (String, Int) -> (String, Int) -> IO ()
> update_best_with ref (current_name, current_count) = do
> 	(best_name, best_count) <- readIORef ref
> 	when (current_count > best_count) $ do
> 		writeIORef ref (current_name, current_count)

> do_next :: IORef (String, Int) -> String -> IO ()
> do_next ref name = do
> 	count <- get_length name
> 	update_best_with ref (name, count)

> longest_file :: [String] -> IO (String, Int)
> longest_file names = do
> 	ref <- newIORef ("none", 0)
> 	mapM_ (do_next ref) names
> 	readIORef ref

A more elegant solution would be to generate a list of filename/length
pairs (e.g. using mapM), then just use maximumBy to select the
longest, e.g.

> get_length' :: String -> IO (String, Int)
> get_length' name = do
> 	length <- get_length name
> 	return (name, length)

> longest_file :: [String] -> IO (String, Int)
> longest_file names = do
> 	pairs <- mapM get_length' names
> 	return $ maximumBy cmp pairs
> 		where	cmp (_, l1) (_, l2) = compare l1 l2

-- 
Glynn Clements <glynn.clements@virgin.net>