Haskell: How to use a where statement under a do block

Viewed 113

I was making a program with Haskell and it's IO, when I came across an error I don't understand. When I use a where statement after a do block it does not do the same thing as without the do block.

The program that works is:

import Control.Monad
prog :: IO()
prog = do m <- getLine
          n <- getLine
          p <- getLine
          replicateM_ (read m :: Int) (putStrLn n)
          replicateM_ (read m :: Int) (putStrLn p)

But when I replace the read m :: Int with a where statement like this:

import Control.Monad
prog1 :: IO()
prog1 = do m <- getLine
           n <- getLine
           p <- getLine
           replicateM_ (a) (putStrLn n)
           replicateM_ (a) (putStrLn p)
           where
           a = read m :: Int 

I get the error:

Template.hs:23:21: error: Variable not in scope: m :: String
   |
23 |            a = read m :: Int 
   |                     ^

I have looked what the problem could be, and I think it has to do with the type of m, which is IO String. I know you have to stay in the IO type (once you are in it) to be able to work with the string. But I don't understand why the 'where' would "break out" of this IO type. To my understanding the two examples I gave are functional the same. First I thought that the error wouldn't be fixed by writing the program without the where, because the function read is from the type read :: Read a => String -> a and my input in the first program is also IO String. So why didn't my first program give me an error? Could someone explain what I understand wrong and how I can fix my program so I only have to execute read m :: Int once? Just some tips of how to use a where statement under a do block would also help.

The original program I got the problem in is longer and not all relevant so I used this minimal working example to explain the essence of my question. In my original program I have multiple statements after where, so I don't want to substitute it all like I did in this example.

1 Answers

The bindings in the do block are opaque to the where statement after it, so you can't reference anything defined in the do block inside the where statement. You don't need to either, since you can use let directly inside do:

prog1 = do m <- getLine
           n <- getLine
           p <- getLine
           -- alternatively: [m, n, p] <- replicateM 3 getLine
           -- use a let statement
           let a = read m :: Int
           replicateM_ a (putStrLn n)
           replicateM_ a (putStrLn p)
Related