跳转到内容

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 程序员普遍认为有意义的缩进可以使代码更易读,但了解如何从一种风格转换为另一种风格可以帮助你理解缩进规则。整个布局过程可以用三个翻译规则(加上一个不常用的第四个规则)概括起来。

  1. 如果你看到一个布局关键字(letwhereofdo),就在它后面的东西之前插入一个左花括号。
  2. 如果你看到一些缩进到相同级别的代码,就插入一个分号。
  3. 如果你看到一些缩进到更低级别的代码,就插入一个右花括号。
  4. 如果你在列表中看到一些意外的东西,比如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

注释

  1. 参见 Haskell 报告(词法单元) 的第 2.7 节关于布局的内容。
华夏公益教科书