你可以想象Haskell和Python的縮進類似,但也有幾個小的差異。
但是,最大的區別在於,Python的縮進敏感語法總是在新行上啓動對齊塊,而Haskell具有帶有對齊塊的語法結構,允許它們通過現有行的一部分開始。這在佈局規則上並沒有什麼不同,但它會顯着影響您對它們的看法(Haskellers不會將規則簡化爲頭腦中的「縮進級別」)。
這裏的一些(可怕)佈局敏感語法的例子在Python:
if True:
x = 1
while (
not x
):
y = 2
的if
和while
構建體隨後一套對齊語句。字符下一個語句的第一個非空白字符必須縮進比外部塊的對齊位置更遠的某個位置,併爲同一內部塊中的所有後面的語句設置對齊方式。每條語句的第一個字符必須與封閉塊的某個位置對齊(這決定了它是哪個塊的一部分)。
如果我們在對齊位置0處添加了一個z = 3
,它將成爲全局「塊」的一部分。如果我們將它添加到位置4,它將成爲if
塊的一部分。如果我們將它添加到位置5,它將成爲while
塊的一部分。在任何其他位置開始語句將是一個語法錯誤。
還要注意,有多行構造的對齊是完全不相關的。在上面,我使用括號在多行上寫了while
的條件,甚至將行與not x
對齊到位置0.甚至沒有關係,引入縮進塊的冒號位於「錯誤對齊」行;縮進塊的相關對齊是while
語句(位置4)的第一個非空白字符的位置,以及下一個語句(位置5)的第一個非空白字符的位置。
這裏的一些(可怕的)佈局敏感哈斯克爾:
x = let
y = head . head $ do
_ <- [1, 2, 3]
pure [10]
z = let a = 2
b = 2
in a * b
in y + z
在這裏,我們有let
(兩次)和do
引入準塊。 x
的定義本身是組成模塊的定義的「塊」的一部分,並且要求位於位置0.
let
塊中第一個定義的第一個非空白字符設置所有對齊塊中的其他定義。 let
塊的外部位置是y
。但是,在開始縮進塊之前,let
的語法不需要換行符(因爲Python的縮進構造全部通過用冒號和新行結束「標題」來完成)。內let
塊具有a = 2
緊接let
以下,但a
位置仍然設置爲塊中的(11)其他定義所需的對準。
再有東西可以拆分在哪裏對齊線不需要多行。在Haskell中,幾乎所有不是特定佈局敏感構造的東西都可以做到這一點,而在Python中,只能使用圓括號或使用反斜槓結束行。但是在Haskell中,構成構造一部分的所有行都必須比它們所在的塊更加縮進。例如,我可以將z
定義的in a * b
部分放在單獨的行上。該in
是let
語法結構的一部分,但它是由let
介紹定義排列塊的不一部分,所以它有沒有特別的對齊要求。然而z = ...
整個定義是定義外let
塊的一部分,所以我不能在3位或更早開始in a * b
線;它是z
定義的「延續線」,所以需要比該定義的開頭縮進得更遠。這與Python的延續線不同,後者對縮進根本沒有限制。
的do
還引入了一個對齊的塊(「聲明」,而不是定義的)。我可以立即按照do
的第一條語句執行,但我選擇了開始新的一行。這裏的塊很像一個Python風格的塊;我必須在比外部塊(外部位置3的外部let
的定義)進一步縮進的某個位置處開始它,並且一旦我完成了,do
塊中的所有語句必須對齊到相同的位置(14 , 這裏)。由於pure [10]
之後的下一行是從位置3開始的z = ...
,所以它隱式地結束了do
塊,因爲它與let
塊的定義對齊,而不是do
塊的語句。
在您的例子:
fib :: Integer -> Integer
fib n = fib_help n 0 1
where fib_help n a b
|n<=1 = b
|otherwise fib_help (n-1) b (a+b)
需要對準的構建體是where
,介紹的很像let
定義的塊。使用Python風情的街區,你總是開始一個新塊之前開始一個新行,你的榜樣應該是這樣的:
fib :: Integer -> Integer
fib n = fib_help n 0 1
where
fib_help n a b
|n<=1 = b
|otherwise fib_help (n-1) b (a+b)
這使得錯誤在你更跳出來。您沒有在4個空格的下一個「縮進級別」中啓動where
中的定義塊,您已在位置10處開始它!然後回到第8位以獲得下一個「縮進級別」。
如果你是Python風格「縮進水平」比哈斯克爾式排列,簡單地格式化您的Haskell塊的Python需要你設置其塊的方式更舒適的思維;在「標題」引入一個塊之後,總是結束該行,然後在下一個「製表位」處開始下一行的塊。
謝謝!現在我懂了! – Schytheron