Error: parse error on input '}' in do notation

Hi,
While playing around with IO and the do notation, I stumbled across an interesting error.
The following cannot be compiled and results in an “error: parse error on input ‘}’” error:

strlen = do { putStr "Enter"; input <- getLine; let n = show (length input); putStrLn (n ++ " characters")}

However, if you add the in after the let, or add a new line after the let expression, it compiles without errors.

strlen = do { putStr "Enter"; input <- getLine; let n = show (length input) in putStrLn (n ++ " characters")}
strlen = do { putStr "Enter"; input <- getLine; let n = show (length input)
;putStrLn (n ++ " characters")}

Is there a reason for this behavior or is this even a compiler bug?
I tested this with GHC 9.4.8.

1 Like

Yeah it’s weird, but the rule is once you’re in braces mode you have to use braces for everything, so you need to do this:

strlen = do { putStr "Enter"; input <- getLine; let {n = show (length input) }; putStrLn (n ++ " characters")}

From layout as specified in Haskell2010:

L (t : ts) (m : ms) = } : (L (t : ts) ms) if m∕ = 0 and parse-error(t) 

This rule, for emitting implicit closing braces, requires m/=0 (which would be true, because it can be zero only if the opening brace is explicit)

The parse-error trick means that it, when reading the putStrLn token, can’t insert the closing brace. The requirements are that it

  • would be a parse error to have the putStrLn not followed by a closing brace (not fulfilled, since even though putStrLn alone lacks a =, it is still a valid prefix. I.e. the inner let block could be assigning the identifier putStrLn)
  • and not be a parse error to have a closing brace inserted before putStrLn (fulfilled)

So in summary, it has only one token look-ahead, which results in too many tokens getting included into the inner layout. When the final explicit closing brace appears, these are our options:

pattern generation note
L (} : ts) (0 : ms) = } : (L ts ms) (Note 3)
L (} : ts) ms = parse-error (Note 3)

The first rule can’t fire, since we’re in an implicit context. So the second rule fires and we have the parse-error.

Have I understood this right?