Haskell的縮進

關於Haskell代碼的縮進,在 real world Haskell以及其他 Haskell的教程中只泛泛介紹了一些,還是有些讓人迷惑。有人推薦了一篇網文,專門介紹Haskell的縮進,說的比較詳細。原文在此:http://en.wikibooks.org/wiki/Haskell/Indentation 我試着翻譯了一下,如下。

Haskell縮進

    Haskell依靠縮進來簡化冗長的代碼,但它的縮進規則會讓人有點兒迷惑,看起來很多而且很隨意。其實混亂來自於規則在實際代碼中的應用,而規則本身很簡單,只有一兩條。所以我們拋開實際中的縮進與排版的混亂表象,來看看規則的本質。
    1 縮進的黃金規則
    儘管本文要詳細講解Haskell的縮進體系,但是請記住一個最基本的規則:作爲表達式的一部分代碼,一定要比表達式的開頭再縮進一些。
    這是什麼意思?看一個簡單的let綁定表達式的例子。綁定變量的等式是let表達式的一部分,因此它要比作爲表達式開頭的let關鍵字更縮進一些。
.-----------------------------
|   let
|     x = a
|     y = b
------------------------------
    你當然可以只縮進一個空格,不過一般都會把第一個等式放在let的同一行,並把其餘行縮進到與第一個等式對齊:
.-----------------------------
|   let x = a
|       y = b
------------------------------
    還有更多的例子:
.-----------------------------
|   do foo
|      bar
|      baz
|    
|   where x = a
|         y = b
|
|   case x of
|       p  -> foo
|       p' -> baz
 ------------------------------   
    注意不像do和where表達式,在case表達式中,把第二行放到case的同一行沒有意義。還有注意箭頭(->)也被對齊了,這只是爲了看上去漂亮一些,不是縮進排版所必須的。只有一行開頭的空白纔會改變排版,造成代碼的不同含義。
    如果一個表達式的開頭並沒有在一行的開始,縮進就稍微複雜一些。其實後面的代碼只需比包含表達式開頭的行更縮進一些就行。
.-----------------------------------------------------------------------------------------
|   myFunction firstArgument secondArgument = do --'do'不在一行開始
|       foo                                      --這些行只需比包含'do'的行更縮進一些就行
|       bar
|       baz
------------------------------------------------------------------------------------------
    上面的例子也可以這樣寫:
.-------------------------------------------------------
|   myFunction firstArgument secondArgument =
|       do foo
|          bar
|          baz
--------------------------------------------------------
    或者:
.-------------------------------------------------------
|   myFunction firstArgument secondArgument = do foo
|                                                bar
|                                                baz
 -------------------------------------------------------                                                
    2 兩種風格的轉換 
    信不信縮進排版也不是必須的?是的,像C語言一樣,Haskell也可以用分號來分割語句,用花括號來組織代碼塊。這種形式不僅常用,而且通過學習如何轉換兩種排版風格也有助於理解Haskell的縮進規則。首先要理解兩個事情:何時需要分號和花括號,如何根據排版來轉換。轉換過程可以總結爲3條規則(加上一條不太常用的第4條):
    (1)遇到一個排版關鍵詞(let,where,of,do),加一個左花括號。
    (2)遇到具有相同縮進的句子,加一個分號。
    (3)遇到一個退回縮進的句子,加一個右花括號。
    (4)在list中遇到異常的句子,如where,不加分號,改加成右花括號。
    練習1:用一個詞回答:遇到一個更爲縮進的句子時該加什麼?
    練習2:將下面的縮進排版翻譯成分號花括號排版。注:有些語句可能不是Haskell的合法語句,本練習只是爲了加強對翻譯過程的理解。
    of a
       b
        c
       d
    where
    a
    b
    c
    do
   you
    like
   the
  way
   i let myself
          abuse
         these
   layout rules

    3 試試排版
.-------------------------
|錯誤的
|-------------------------
|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中的do
    當if中出現do表達式時該怎麼做?上面說過了,任何除過那4個排版關鍵字的符號,包括if then 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關鍵字告訴Haskell插入一個左花括號,花括號卻是取決於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
---------------------------

    do中的if
    這段代碼絆倒了許多Haskell程序員,看看爲什麼代碼塊是錯的呢?

    --爲什麼是錯的?
    do first thing
        if condition
        then foo
        else bar
        third thing

    強調一下,這段代碼中的if then else沒有錯,問題是當在do代碼塊中時,do注意到then語句部分和if語句部分有相同的縮進,導致它認爲then語句是一個和if並列的新的語句。去掉語法糖,這就和其下邊的寫法一樣:
.-------------------------
|一般排版
|-------------------------
|--錯誤版本
|do first thing
|   if condition
|   then foo
|   else bar
|   third thing
---------------------------

.-------------------------
|去掉語法糖後
|-------------------------
|--錯誤版本
|do { first thing
|   ; if condition
|   ; then foo
|   ; else bar
|   ; third thing }
---------------------------

    這種寫法使得編譯器只看到了if condition;一樣的語句,認爲if表達式沒有被完成,因此難怪會出錯。改正方法就是把if塊中的其他語句向後縮進一些,如下:
.-------------------------
|一般排版
|-------------------------
|--修正後的版本
|do first thing
|   if condition
|     then foo
|     else bar
|   third thing
---------------------------

.-------------------------
|去掉語法糖後
|-------------------------
|--修正後的版本
|do { first thing
|   ; if condition
|       then foo
|       else bar
|   ; third thing }
---------------------------

    if後的縮進防止了do代碼塊錯誤的將then部分解釋爲一個新的語句。你也可以對每一個then else進行這樣的縮進,雖然有時候這並不是必須的,但會避免很多歧義,使程序更清晰。
    練習:do中if的縮進問題困擾了很多Haskell編程者,有人向Haskell的主要設計人員提議在if then else中也增加分號,你覺得這會有什麼樣的作用?
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章