Haskell/缩进
Haskell 依靠缩进来减少代码的冗长。尽管在实践中有一些复杂性,但实际上只有几个基本的布局规则。[1]
属于某个表达式的一部分的代码应该比该表达式的开头缩进更多(即使该表达式不是该行的最左侧元素)。
最简单的例子是 'let' 绑定组。绑定变量的等式是 'let' 表达式的一部分,因此应该比绑定组的开头缩进更多:'let' 关键字。当你在单独的行上开始表达式时,你只需要缩进一个空格(尽管多个空格也是可以接受的,并且可能更清晰)。
let
x = a
y = b
你也可以将第一个子句与 'let' 放在一起,只要你将其他子句缩进对齐。
错误 | 错误 | 正确 |
---|---|---|
let x = a
y = b
|
let x = a
y = b
|
let x = a
y = b
|
这往往会让很多初学者感到困惑:所有分组的表达式必须完全对齐。在第一行,Haskell 将表达式左侧的所有内容都算作缩进,即使它不是空格。
以下是一些更多示例
do
foo
bar
baz
do foo
bar
baz
where x = a
y = b
case x of
p -> foo
p' -> baz
请注意,对于 'case',将第一个子表达式放在与 'case' 关键字相同的行上并不常见(尽管它仍然是有效的代码)。因此,case 表达式中的子表达式往往只比 'case' 行缩进一个步骤。另请注意我们是如何将箭头对齐的:这纯粹是美观的,不算是不同的布局;只有缩进(即从最左侧边缘开始的空格)会影响布局的解释。
当表达式的开头不在行的开头时,事情会变得更加复杂。在这种情况下,只需比包含表达式开头的行缩进更多即可。在以下示例中,do
位于行的末尾,因此表达式的后续部分只需相对于包含do
的行缩进,而不是相对于do
本身。
myFunction firstArgument secondArgument = do
foo
bar
baz
以下是一些可行的替代布局
myFunction firstArgument secondArgument =
do foo
bar
baz
myFunction firstArgument secondArgument = do foo
bar
baz
myFunction firstArgument secondArgument =
do
foo
bar
baz
如果你使用分号和花括号来进行分组和分隔,就像在 C 这样的“一维”语言中一样,实际上可以选择不使用缩进。即使 Haskell 程序员普遍认为有意义的缩进可以使代码更易读,但了解如何从一种风格转换为另一种风格可以帮助你理解缩进规则。整个布局过程可以用三个翻译规则(加上一个不常用的第四个规则)概括起来。
- 如果你看到一个布局关键字(
let
、where
、of
、do
),就在它后面的东西之前插入一个左花括号。 - 如果你看到一些缩进到相同级别的代码,就插入一个分号。
- 如果你看到一些缩进到更低级别的代码,就插入一个右花括号。
- 如果你在列表中看到一些意外的东西,比如
where
,就在分号之前插入一个右花括号。
例如,这个定义...
foo :: Double -> Double
foo x =
let s = sin x
c = cos x
in 2 * s * c
...可以不考虑缩进规则而重写为
foo :: Double -> Double;
foo x = let {
s = sin x;
c = cos x;
} in 2 * s * c
在 GHCi 中编写单行代码时,显式使用花括号和分号可能很方便。
Prelude> let foo :: Double -> Double; foo x = let { s = sin x; c = cos x } in 2 * s * c
练习 |
---|
使用显式花括号和分号重写控制结构章节中的这段代码。 doGuessing num = do
putStrLn "Enter your guess:"
guess <- getLine
case compare (read guess) num of
LT -> do putStrLn "Too low!"
doGuessing num
GT -> do putStrLn "Too high!"
doGuessing num
EQ -> putStrLn "You Win!"
|
错误 | 错误 | 正确 | 正确 |
---|---|---|---|
do first thing
second thing
third thing
|
do first thing
second thing
third thing
|
do first thing
second thing
third thing
|
do
first thing
second thing
third thing
|
由于上述“缩进的黄金法则”,do
块中的花括号不是取决于do
本身,而是取决于紧随其后的东西。例如,这段看起来很奇怪的代码块是完全可以接受的。
do
first thing
second thing
third thing
因此,你也可以这样编写 if/do 组合
错误 | 正确 | 正确 |
---|---|---|
if foo
then do first thing
second thing
third thing
else do something_else
|
if foo
then do first thing
second thing
third thing
else do something_else
|
if foo
then do
first thing
second thing
third thing
else do
something_else
|
这不是关于do
,而是关于将do
内所有位于相同级别的项目对齐。
因此,以下所有内容都是可以接受的。
main = do
first thing
second thing
或
main =
do
first thing
second thing
或
main =
do first thing
second thing
注释
- ↑ 参见 Haskell 报告(词法单元) 的第 2.7 节关于布局的内容。