Do statement under a where clause
Asked Answered
E

4

0

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 ?

Ender answered 11/6, 2018 at 18:51 Comment(12)
do-blocks are not statements, they are values. What do you think this is supposed to do? Like I said in your last question, please study monads before using them!Populous
@WillemVanOnsem I have used to that kind of words. What I meant is that I want to declare a new value of type [String] from the value of type IO [String].Ender
@AJFarmar I'm just somehow trying to comply that last 2 line of code in the definition of the function, that is it.Ender
to deal with indentation, you can use do { x ; y ; z } explicit syntax.Rhizo
Change your function to be [String] -> [[String]] and then just use fmap to lift it into the IO monad.Phlebitis
@AJFarmar Before learning the inner workings of the language, I would like to knock my head againts the constraints of the language a bit. Without it, there is a little motivation to learn.Ender
you can't use a variable bound in your do block (i.e. words') outside the do block (i.e. in a guard). you will have to restructure your code.Rhizo
@onurcanbektas Monads are not 'the inner workings of the language', they are a vital feature of Haskell that defines its I/O constraints. Without an understanding of monads, you will not be able to write good Haskell. Learn about monads!Populous
@AJFarmar while true, a do notation can be mastered on its own, as a DSL in its own right, with few rules to follow. this could be a first, smaller step to master. (also, including all the optional parens, braces and semicolons everywhere (do, case, $, ...) can really help a beginner by removing any additional uncertainty with the syntax they might still have).Rhizo
@AJFarmar The reason why I haven't learned monads is that I'm math student, and I would first like to learn Category theory, and then come back and learn monads etc.Ender
@onurcanbektas I'm also a math student, and I learned monads way before I began. Trust me, you're not losing out. If you're continuing to use Haskell, learn monads.Populous
What is the reason for the downvote ?Ender
P
4

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.

Populous answered 11/6, 2018 at 19:2 Comment(6)
I just directly copy-paste the code, but it throws 2 errors becuase in (:) it is expected [String], and not IO [String]; this is at least what ghci says.Ender
Ah, this is a flaw due to the original definition, updating now. (Updated now)Populous
But still the argument of decompEventBlocks in rest <- ... is [String ]?Ender
By the way, the code is giving error (which is the reason for the my question above.)Ender
@onurcanbektas I get no error when running this code verbatim. If you're getting an error, find the code causing it and post another question elsewhere.Populous
Ok, thanks for the answer. (PS: I have decided to get rid of IO's and edit my orijinal code with the style of your code.)Ender
M
5

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'
Magus answered 11/6, 2018 at 18:58 Comment(0)
P
4

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.

Populous answered 11/6, 2018 at 19:2 Comment(6)
I just directly copy-paste the code, but it throws 2 errors becuase in (:) it is expected [String], and not IO [String]; this is at least what ghci says.Ender
Ah, this is a flaw due to the original definition, updating now. (Updated now)Populous
But still the argument of decompEventBlocks in rest <- ... is [String ]?Ender
By the way, the code is giving error (which is the reason for the my question above.)Ender
@onurcanbektas I get no error when running this code verbatim. If you're getting an error, find the code causing it and post another question elsewhere.Populous
Ok, thanks for the answer. (PS: I have decided to get rid of IO's and edit my orijinal code with the style of your code.)Ender
K
0

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 = ...
Kaylor answered 11/6, 2018 at 23:17 Comment(2)
So, words' <- words is not a declaration ? but why ?Ender
@onurcanbektas Becase words' <- words; b is syntactic sugar for words >>= \words' -> b. I explained this in my answer.Populous
R
0

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 ms 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.

Rhizo answered 12/6, 2018 at 10:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.