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中也增加分號,你覺得這會有什麼樣的作用?
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.