Layout Rule
Helium
The layout rule makes the braces and semi-colons you often see in other
languages superfluous. The layout of your program tells the compiler where
definitions and blocks of definitions end. Let us look at an example:
main = let sieve (x:xs) = x : sieve
(filter ((/= 0).(`mod` x)) xs)
allPrimes = sieve [2..]
in take 100 allPrimes
The definitions for
sieve and
allPrimes are indented to
align vertically. This tells the compiler that these are definitions at the same
level. The argument to
sieve on the second line is indented more than
the definition of
sieve, telling the compiler that it is a
continuation of the first line. The keyword
in is indented less than
the definitions and this tells the compiler that the block of definitions has
ended.
The layout rule starts working whenever it sees one of the following keywords:
do,
let,
where,
of. It also works at
top-level when you don't have a module header. The first symbol it sees after it
is started (the underlined
sieve in the example) determines the
column with which indentation of subsequent lines is compared.
The layout rule in short:
- same indentation as the first in the block means a new definition/statement/alternative
- more indentation means that this line belongs to the last line
- less indentation means end of a block of definitions/statements/alternatives
The special case of Let-expressions
The layout rule has a special case for
let expressions so that the
keyword
in also ends the block of definitions. This means you can
write:
main = let divisible x y = x `mod` y == 0 in divisible 10 2
In the sieve example we could have used this special case and write "
in
take 100 allPrimes" behind "
sieve [2..]" but
that doesn't improve readability.
Some error messages about wrong layout
Since layout has a meaning in Helium you can also make mistakes that have to do
with layout. In languages where there is no layout, the layout is purely meant
to improve readibility for humans. In the case of Helium, the layout rule takes
over the role of braces and semi-colons and making a layout mistake is like
forgetting a semi-colon or writing too many closing braces. Let us see what
happens:
main = let sieve (x:xs) = x : sieve
(filter ((/= 0).(`mod` x)) xs)
allPrimes = sieve [2..]
in take 100 allPrimes
In this example
allPrimes is indented less than the first in the
block (
sieve). This means that the block started by
let
ends here. But when a
let block ends there should be the keyword in
and not the name
allPrimes. And that's exactly what Helium tells you:
(3,10): Syntax error:
unexpected variable 'allPrimes'
expecting keyword 'in'
Now the other way around. Let us indent
allPrimes too much:
main = let sieve (x:xs) = x : sieve
(filter ((/= 0).(`mod` x)) xs)
allPrimes = sieve [2..]
in take 100 allPrimes
Indenting more means that the line belongs to the previous line, the definition
of
sieve in this case. Therefore, the compiler looks at the
definition of sieve as:
sieve (x:xs) = x : sieve (filter ((/= 0).(`mod` x)) xs) allPrimes = sieve [2..]
The variable
allPrimes is okay in this context; it is seen as an
extra parameter to
sieve. If the compiler would get to type checking
this would result in a type error, but the compiler doesn't get to that stage.
The next symbol, the equals sign, comes as a total surprise; it can not occur in
this context:
(3,25): Syntax error:
unexpected '='
expecting operator, constructor operator, '::', keyword 'where',
next in block (based on layout), ';' or
end of block (based on layout)
The Helium compiler tells you it doesn't expect the '
=' and also
tells you all the things it considers acceptable in this context. You see that
it would consider a
next in block (based on layout) acceptable; in
other words: a new definition that is indented the same. It would also be happy
with an end of block and some other things but not with the '
='.
Tabs are Evil
In short, don't use tab characters in your Helium source files. Your editor and
the Helium compiler may interpret tab characters differently. In the Helium
compiler a tab takes you to the next tab stop and tab stops are 8 characters
apart. If your editor has differently sized tab stops, your program may look
okay in your editor, but still the Helium compiler gives layout-related
messages.
The solution is to configure your editor to convert existing and new tabs to
spaces. This way no tab characters will appear in your source code and the
Helium compiler sees the same layout as you do.
Differences with Haskell's Layout Rule
The Haskell layout rule is more powerful than the one in Helium. In Haskell, a
block may also be terminated by a symbol that implies that the end of the block is
reached. Let us look at an example again:
main = ( case 3 of 4 -> 5
3 -> 5, 6 )
The layout block started at
4 is ended at the comma because a comma
is unexpected here, but if the block is closed here, it is okay to be there. The
value of
main is thus the tuple
(5, 6). In Helium, to end
a block you really have to start on a new line that is indented less. The
following syntax error is reported:
(2,26): Syntax error:
unexpected ','
expecting expression, operator, constructor operator, '::',
keyword 'where', next in block (based on layout),
';' or end of block (based on layout)
The comma is unexpected and suggestions are made what can be expected as this
place. One of those suggestions is an end of block (based on layout) and that's
the one we meant:
main = ( case 3 of 4 -> 5
3 -> 5
, 6 )
Or you can use explicit layout.
Explicit Layout
You can circumvent the layout rule and use explicit layout with braces and
semi-colons. After the keywords
do,
let,
where,
of and at the beginning of a module
you can write an opening brace. Definitions / alternatives / statements can then
be separated by semi-colons and the block is ended by a closing brace. You can
choose explicit layout locally:
main = ( case 3 of { 4 -> 5
; 3 -> 5 }, 6 )
or for your whole program:
{ main = take 100 allPrimes;
sieve (x:xs) = x : sieve(filter ((/= 0).(`mod` x)) xs);
allPrimes = sieve [2..]
}
As you can see, inside a context of braces layout doesn't matter anymore.
--
JurriaanHage - 07 Apr 2008