I'm trying to convert IO [String]
to [String]
with <-
binding; however, I need to use a do
block to do that under a where
statement, but Haskell complains about the indentation all the time. Here is the code:
decompEventBlocks :: IO [String] -> IO [[String]]
decompEventBlocks words
| words' /= [] = block : (decompEventBlocks . drop $ (length block) words')
| otherwise = []
where
do
words' <- words
let block = (takeWhile (/="END") words')
What is the reason for that ? And how can we use do
block in a where
statement ? Moreover, is there any chance that we can have some statements before the guards ?
Remember: do
-blocks are syntactic sugar for monadic notation. This means the following applies:
do {a; b} = a >> b
dp {a <- b; c} = b >>= \a -> c
In other words, when using do
-notation, you are actually producing values. This is why you can't just have a do
-block in the top level of your where
statement.
The way to solve this is to put the function into a do
-block:
decompEventBlocks :: IO [String] -> IO [[String]]
decompEventBlocks words = do
-- We unwrap the IO [String], but we keep it in the do-block,
-- because it must be kept in a monadic context!
words' <- words
let block = (takeWhile (/="END") words')
-- This is equivalent to the guards you had in your function.
-- NB return :: Monad m => a -> m a, to keep it in a monadic context!
if not $ null words'
then do
-- Since the recursion is monadic, we must bind it too:
rest <- decompEventBlocks $ return $ drop (length block) words'
return $ block : rest
else return []
To learn about monads, do
-notation, >>=
, and >>
, I highly reccommend reading the LYAH chapters to gain a good understanding before attempting more monadic code.
You cannot convert for IO String to a String.
What you can do, however, is bind the contents of IO String to a 'variable', but that will still result in the whole computation being embedded inside IO.
foo = do
x <- baz -- here baz is the IO String
let x' = doStuff x
return x' -- embeds the String inside IO, as otherwise the computation would result in IO ()
To answer your question
foo x = baz x -- x here is your 'IO String'
where
baz x = do
x' <- x
return $ doStuff x'
As a slightly different angle on AJFarmar's answer: The only things you can have in a where
are declarations. do
blocks aren't declarations, they are expressions. I.e. this is the same as if you tried to write where 2+5
. If you want to declare block
in a where
, it must be
where
// can have other declarations, even mutually recursive
block = ...
Do notation is used to write expressions of the general form
ex :: Monad m => m t
let ex = do
{ x <- foo -- foo :: Monad m => m a, x :: a
; y <- bar x -- bar x :: Monad m => m b, y :: b
; z <- baz x y -- baz x y :: Monad m => m c, z :: c
; quux x y z -- quux x y z :: Monad m => m t
}
Notice all the m
s are the same, and a
, b
, c
, ... can be different, though the t
in the last do sub-expression's type and the overall do expression's type is the same.
The do notation variables are said to be "bound" by the <-
construct. They come into scope at their introduction (to the left of <-
) and remain in scope for all the subsequent do sub-expressions.
One built-in monadic expression available for any monad is return :: Monad m => a -> m a
. Thus x <- return v
binds x
to v
, so that x
will be available in the subsequent sub-expressions, and will have the value of v
.
All the do variables are confined to that do
block, and can not be used outside it. Each variable's scope is all the code in the same do
block, below / after the variable's binding.
This also means that <-
's is a non-recursive binding, since the variable can't go on its right hand side as well as the left: it will be two different variables with the same name, in that case, and the variable on the right will have to have been established somewhere above that point.
There are a few general patterns here:
do { _ <- p ; _ <- q ; r } === do { p ; q ; r }
do { x <- p ; return x } === do { p } === p
do { x <- return v ; foo x } === do { foo v } === foo v
do { p ; q ; r } === do { p ; do { q ; r } }
=== do { do { p ; q } ; r }
do { x <- p ; === do { x <- p ;
y <- q x ; z <- do { y <- q x ;
return (foo x y) } return (foo x y) } ;
return z }
All the Monad m => m a
expressions are just that, expressions, and so can in particular be an if - then - else
expressions with both the consequent and the alternative branches being of the same monadic type (which is often confusing for beginners):
do { x <- p ;
y <- if (pred x) then (foo x) else (bar x) ;
return (baz x y) }
update: One of the main points of the monad is its total separation of effects from the pure calculations. Once in a monad, you can't "get out". Monadic computations can use pure calculations, but not vice versa.