Yesterday I was struggling with some code in a do-block where the type checker kept complaining about Unexpected or mismatched indentation. I was starring myself blind at the problem and eventually just pulled out the code to its own function instead -and things worked fine…
Today I ran into a similar problem again, and randomly I decided to give the code a little more indentation, and then the type checker magically stopped complaining.
A simplified silly example to show the problem:
fourspaces :: Effect Unit
fourspaces = do
let stuff =
[40,41,42,43,44] -- this line is indented with four spaces
# Array.filter (\x -> x >= 42)
<#> (\x -> x + 1)
log $ show stuff
The type checker reports
Unable to parse module:
Unexpected or mismatched indentation
Same example, but with 6 spaces:
sixspaces :: Effect Unit
sixspaces = do
let stuff =
[40,41,42,43,44] -- this line is indented with six spaces
# Array.filter (\x -> x >= 42)
<#> (\x -> x + 1)
log $ show stuff
The type checker have no problems with the version with 6 spaces…
Is this a bug? or is there some logic to why only the six space indentation works?
Its certainly a bit confusing to a beginner
The entire expression bound to bindingName must be indented to the right of the column in which bindingName first appears. So, everything must be to the right of that | symbol. x represents an invalid indentation whereas ✓ represents a valid position:
foo = do
let bindingName =
x |
x |
x |
x |
x |
x|
|
|✓
| ✓
People usually newline-then-indent the reference name for expressions that span multiple lines because it reduces the amount of indentation needed for the expression (5-6 spaces below vs 7-8 spaces above):
To elaborate a bit on why that’s the case: it’s because PureScript has a concept of binding groups. Multiple bindings under a single let form a single binding group, where each binding in the group is in scope within the entire group.
example = do
let
f = ...
g = ...
...
So f and g are in scope within the definitions of f and g. However,
example = do
let f = ...
let g = ...
...
These are separate binding groups, where f is in scope for g, but g is not in scope for f. This is in contrast to something like JavaScript, which treats an entire block as a single binding group (for let and const), or a function body as a binding group (for var).
function example () {
let f = ...;
let g = ...;
}
Here f and g are similar to the first example, as they are both in scope for both bindings, though they may still be undefined depending on when they are accessed.
Thank you that makes sense!
Hope authors of educational material remembers to mention it explicitly. I did see code with lots of indentation after let bindings in the book I’m reading but never really thought about it and just assumed any indentation would do fine
Yes, it avoids ambiguity in the grammar. All whitespace sensitive languages have the same basic rule (which is generally referred to as the offside rule). At an implementation level, the lexer scans the input token stream, inserting implicit delimiters for whitespace (we call this layout). You can see some of the layout golden tests which makes these implicit delimiters visible by printing them as braces and semicolons.