2

I have entered some code in ghci, similar to this:

main = do { a <- getLine ; let b = "Hello " ++ a ; putStrLn b }

However, I get this error:

<interactive>:1:63: error: parse error on input `}'

In previous versions of Haskell/GHC, I remember this working just fine - it was even explicitly said that, in do blocks, you don't need the in keyword. Yet, the only way to get this to work seems to be:

main = do { a <- getLine ; let b = "Hello " ++ a in putStrLn b }

which doesn't produce this error.

Has this been removed? If so, do I need a second do block inside the let in expression?

Will Ness
  • 70,110
  • 9
  • 98
  • 181
schuelermine
  • 1,958
  • 2
  • 17
  • 31
  • 3
    `main = do { a <- getLine ; let { b = "Hello " ++ a} ; putStrLn b }` works fine. – Willem Van Onsem Jun 05 '19 at 08:26
  • 1
    I think that's parsed as `do { a <- getLine ; let {b = "Hello " ++ a ; putStrLn b} }` if you don't put braces. After all, `do { a <- getLine ; let {b = "Hello " ++ a ; putStrLn b = ()} ; print 3 }` would be legal code. – chi Jun 05 '19 at 08:30

2 Answers2

7

let is a layout keyword like do, both as a statement in a do block and in a letin… expression, because it introduces a block of bindings. This:

main = do
  a <- getLine
  let b = "Hello " ++ a
  putStrLn b

Desugars to this:

main = do {
  a <- getLine;
  let {
    b = "Hello " ++ a;
  };
  putStrLn b;
};

Whereas what you’ve written is equivalent to this:

main = do {
  a <- getLine;
  let {
    b = "Hello " ++ a;
    putStrLn b
  };
};

So naturally GHC is expecting something else—a pattern or =—after putStrLn b, since you could be defining a local function named putStrLn with a parameter named b. The solution is either to use explicit braces in the let statement:

main = do { a <- getLine; let { b = "Hello " ++ a }; putStrLn b }

Or to use multiline mode in GHCi, either with the :{ command, terminated with the :} command:

> :{
| main = do
|   a <- getLine
|   let b = "Hello " ++ a
|   putStrLn b
| :}
>

Or with :set +m, and terminated with a blank line:

> :set +m
| main = do
|   a <- getLine
|   let b = "Hello " ++ a
|   putStrLn b
|
>

Followed by :unset +m to return to single-line mode.

Jon Purdy
  • 53,300
  • 8
  • 96
  • 166
4

The problem is here that it parses your putStrLn b as a let declaration as well, so it parses it basically as:

do { a <- getLine; let { b = "Hello " ++ a ; putStrLn b } }

It thus is looking for a = in the putStrLn part, where you will define the putStrLn function. The parser thus has the "idea" that you are defining a function, not calling a function.

Indeed, we can write for example:

Prelude> let a = 3; f b = b + 1
Prelude> f a
4

so here we declared two variables in the same line.

You can use curly brackets to make clear that the let is scoped to only b, like:

do { a <- getLine; let { b = "Hello " ++ a }; putStrLn b }

The priority of let is due to the grammar, defined in Chapter 3: Expressions in the Haskell'10 report.

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555