Question

In this question Will's answer states that the following code (let's call it code A):

reverse2lines :: IO () 
reverse2lines = 
 do line1 <- getLine 
    line2 <- getLine
    putStrLn (reverse line2) 
    putStrLn (reverse line1)

can be transformed into the following (let's call it code B) :

reverse2lines = 
 do { line1 <- getLine ;
      do { line2 <- getLine ;
           do { putStrLn (reverse line2) ;
                do { putStrLn (reverse line1) } } } }

I am confused. I understand, for example, that

addOneInt :: IO () 
addOneInt = do line <- getLine
               putStrLn (show (1 + read line :: Int))

can be transformed into:

addOneInt' :: IO ()
addOneInt' = getLine >>= \line ->
             putStrLn (show ( 1 + read line :: Int))  

But I don't understand how the nested do transformation works. In other words, I don't understand how one arrives from code A to code B ? What are the rules governing this transformation ?

Where are these rules described / explained / specified ?

Could someone please explain what is going on here with this (codeA to codeB) transformation ?

For example, what does do { command1; do {command2 } } mean ? How should I interpret that ? What is its definition ?

In other words,

what is the difference between

do {command1;command2} and

do {command1; do {command2}} ?

Furthermore, what is the difference between

do {command1; do{command2};command3} and

do {command1;do {command2; do {command3}}} ?

Thanks for reading.

Was it helpful?

Solution

Just treat the do expressions completely separately: nesting them does not change how they get desugared. For your to example, we can start with the bottom line:

reverse2lines = 
 do { line1 <- getLine ;
      do { line2 <- getLine ;
           do { putStrLn (reverse line2) ;
                putStrLn (reverse line1) } } }

then the next one:

reverse2lines = 
 do { line1 <- getLine ;
      do { line2 <- getLine ;
           putStrLn (reverse line2) >> putStrLn (reverse line1) } }

which is, in fact, just like:

reverse2lines = 
 do { line1 <- getLine ;
      do { line2 <- getLine ;
           putStrLn (reverse line2)
           putStrLn (reverse line1) } }

then we can turn the inner remaining do into a lambda:

reverse2lines = 
 do { line1 <- getLine ;
      getLine >>= \ line2
       putStrLn (reverse line2) >>
       putStrLn (reverse line1)  }

And then, if we go backwards, we see that it's the same as just:

reverse2lines = 
 do { line1 <- getLine ;
      line2 <- getLine ;
      putStrLn (reverse line2) ;
      putStrLn (reverse line1)  }

So, as you can see by going through the whole example, the nested version is the same as the flat version. In fact, if you just look at the rules for desugaring do expressions (and we've seen the important ones), you'll see that nesting them like this does not change anything.

In fact, this is pretty natural because the way do is desugared is defined recursively: each time we desugar a single line, we recurse on all the lines following it as if they were another do expression.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top