All block structure languages use indentation and whitespace to show the logical structure of the code and improve readability. So much so that most sourcecode editors have features/plugins to align code. Some languages recognise newlines as equivalent to ;
statement-separator; but very few also recognise newline as {
or }
depending on context.
So Haskell has a problem for newbies coming from other block-structured languages: layout is crucial; getting it wrong will make a mess of parsing – it won’t just make your code less pretty.
GHC tries to spot where bad layout might be the cause of a mis-parse. But it’s not always successful. And anyway detecting it at compile time is too late: you really want the editor to prompt you. OTOH, it’s not reasonable to expect an editor to parse your whole source. I’m wondering if there’s some heuristics that can be detected locally – say by just comparing two adjacent lines?
To be helpful, the heuristics should have a high probability of detecting trouble – we don’t want to cry ‘wolf’ too often. Also this should tolerate different coding styles/not impose ‘one right way’.
Here’s some recent examples where GHC failed to detect the problem was bad layout: from StackOverflow; and adapted from a Discourse q. (Yes there’s lots of other problems with these examples as you’d expect from newbies, not just bad layout, but pointing out the bad layout would/should have been the best help.)
-
nextTool :: Tool -> Tool nextTool l = case l == nothing of ... ===> • Expected kind ‘k0 -> k1 -> *’, but ‘Tool’ has kind ‘*’
-
main :: IO () main = do putStrLn "Hello World" xs::Array Int Int xs = array ===> parse error on input ‘=’ ===> Perhaps you need a ‘let’ in a ‘do’ block?
In the first example, the underneath line (beginning nextTool
) is indented when it shouldn’t be; in the second example, the line underneath do
is not indented when it should be.
I’m thinking editors at least know the keywords/symbols of a language, for colour-coding. Then:
- Haskell has a bunch of keywords/symbols such that if they appear at the end of a line, the next line should be relatively indented (
let ... in
,case ... of
,... where
,... =
,do
); or - If they appear at the start of a line, that should be relatively indented (
| ...
guards orwhere ...
); or - If the symbol starting this line is the same as starting the line above, they should be indented the same (
| ...
guards,, ...
multi-line tuples or list exprs). - …
Or look for tell tales that this line can’t be a continuation of the line above, so shouldn’t be relatively indented:
- A line with
=
can’t be a continuation of one with::
(unless that’s nested( ... :: ... )
as in a pattern signature). - Adjacent lines with
=
must form the same decl block, so should be indented the same. - …