Nick Brunt
Nick Brunt

Reputation: 10057

Scope of IO defined variables

I've discovered that when doing IO in Haskell, variables that are assigned using the <- operator are only in scope for the statements immediately after them - not in where clauses.

For example:

main :: IO()
main =  do
          s <- getLine
          putStr magic
            where
              magic = doMagic s

This won't work as s is not in scope. I did some research to confirm it and found this article:

Unlike a let expression where variables are scoped over all definitions, the variables defined by <- are only in scope in the following statements.

So how can I make s available for use in the where clause?

Upvotes: 4

Views: 1332

Answers (3)

Zopa
Zopa

Reputation: 668

let is the practical way to handle this. But it may be worth unpacking what's really going on here. The do statement isn't what's causing the scoping trouble.

Remember that

 main =  do
      s <- getLine
      putStr magic
        where
          magic = doMagic s

is equivalent to this:

main = getline >>= \s ->
       putStr magic
          where magic = doMagic s

If we put in some parentheses, this is:

main = getline >>= (\s -> putStr magic) where magic = doMagic s

This is where the scope of s comes from: it's the argument to a lambda expression, and it only exists within that lambda expression. Try to use it in a where clause outside of that lambda, and you get a "not in scope" error.

let works because let and where parse differently. With parentheses for clarity:

foo   = (\x -> let y = x in y)  -- Works fine
foo'' = (\x -> y) where y = x   -- y is not in scope

This is what's causing the problem; it's not specific to IO, or to do statements.

Upvotes: 1

dave4420
dave4420

Reputation: 47052

In addition to the general let form, there's a special let form for use with do syntax you can use instead:

main :: IO()
main =  do
          s <- getLine
          let magic = doMagic s
          putStr magic

magic is available in all following lines in the block.

Upvotes: 6

user268396
user268396

Reputation: 11976

Well, magic is a function. Therefore you can do something like:

magic m = doMagic m

Or:

magic = \m-> doMagic m

And call it like so:

putStrLn $ magic s

Of course, as you already researched, the sensible thing to do when you can reuse your computed magic is to use a let ... in expression, and nest the calls:

let magic_str = magic s in
  putStrLn magic_str

Upvotes: 1

Related Questions