跳轉到內容

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 節關於佈局的內容。
華夏公益教科書