error-messages
error-messages copied to clipboard
Unhelpful error message with top-level let-bindings
From https://reddit.com/r/haskell/comments/pizmp7/haskell_beginner_what_am_i_doing_wrong_i/
The code in question is:
let triangles = [ (a,b,c) | c <- [1..10], b <- [1..10], a <- [1..10] ]
let rightTriangles = [ (a,b,c) | c <- [1..10], b <- [1..c], a <- [1..b], a^2 + b^2 == c^2 ]
If you save this to a file (say Let.hs
) and compile it with ghc
, it will produce this error message:
Let.hs:2:1: error:
parse error (possibly incorrect indentation or mismatched brackets)
|
2 | let rightTriangles = [ (a,b,c) | c <- [1..10], b <- [1..c], a <- [1..b], a^2 + b^2 == c^2 ]
| ^
I think the problem is that it expects the in ...
part of a let ... in ...
expression which can occur at the top level if TemplateHaskell
is enabled. Such expressions require indentation, so I think it complains about that.
It seems to me to be difficult to improve this without hard-coding a check for top-level let
bindings. Maybe that is what we should do? Or perhaps someone can come up with a more general solution.
let triangles = [ (a,b,c) | c <- [1..10], b <- [1..10], a <- [1..10] ] let rightTriangles = [ (a,b,c) | c <- [1..10], b <- [1..c], a <- [1..b], a^2 + b^2 == c^2 ]> I think the problem is that it expects the in ...
part of a let ... in ... expression which can occur at the top level if TemplateHaskell is enabled. Such expressions require indentation, so I think it complains about that.
Is the code above the only code, or are there pragmas or a module declaration as well?
No, that's all the code. I think that GHC's parser tries to be general enough to accommodate TemplateHaskell
even if it is not enabled. If it parses a complete top-level expression then it will produce an error which tells you to enable TemplateHaskell
, but now there is a parse error in the top-level expression, so you get a strange error message on the second line, where I would have expected an error on the first line and perhaps that error message could be more specific about the fact that let expressions are not allowed at the top-level.
My guess is that it wouldn't be hard to do better here: whenever a naked expression is encountered at top-level, we just check for -XTemplateHaskell
and issue an error if it's not set. I guess the challenge is that, here, we have invalid expressions, and so the parser never "returns" from parsing one. Still, I imagine it's possible to do better -- after all, we successfully sometimes put in the (possibly incorrect indentation or mismatched brackets)
bit, so maybe twiddling the logic around there will get us to say something about splices, etc.
In the theory that we can do better, what would better look like? I propose
Let.hs:2:1: error:
parse error at top-level expression
Bare expressions cannot be written as top-level declarations within a module.
Instead, you probably want '<variable-name> = <expr>' to define a variable.
(Exception: GHC's TemplateHaskell extension supports top-level expressions
as declaration splices. To enable these declaration splices, enable TemplateHaskell.)
|
2 | let rightTriangles = [ (a,b,c) | c <- [1..10], b <- [1..c], a <- [1..b], a^2 + b^2 == c^2 ]
| ^
Is that too verbose? Does it make it clear enough that TemplateHaskell
is for specialized applications and should not blindly be enabled? Does anyone spot other improvements?
As I wrote in https://github.com/haskell/error-messages/issues/19#issuecomment-1043979547, I think top-level expressions for TH was a terrible mistake, and we should just get rid of it, rather than add enough special checks to try to undo the damage it causes with bad almost-parses.
let triangles = [ ... ]
we could honestly just parse as a declaration, and then complain it's illegal.
In general, I think having an initial very liberal pare that we then refine into more restrictive ASTs is a great way to make errors better.
The cost is that the liberalized grammar could introduce new ambiguities in previously legal code. I would love a way to avoid that, but I worry it might require new research to figure out how to do systematically.
(Maybe we can just mark some rules as "low priority"? and then it always resolves any ambiguity with them and a regular rule in the regular rules failure?)
As I happy maintainer I should know the answer here :blush:. Happy to discuss the "how" with an issue over there if we agree the "what" is good.