2017-09-01 65 views
3

我剛開始學習Haskell,我簡要地閱讀了一些縮進規則,在我看來,Haskell在縮進方面的行爲就像Python一樣(我可能是錯的)。無論如何,我試圖寫一個尾遞歸斐波納契函數,我不斷收到一個縮進錯誤,我不知道我在哪裏縮進我的代碼錯誤。像Python一樣在Haskell中縮進嗎?

錯誤消息:

F1.hs:6:9: error: 
    parse error (possibly incorrect indentation or mismatched brackets) 
    | 
6 |   |n<=1 = b |  ^

代碼:

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) 

注:我在記事本寫代碼++和我已經改變了設置,以便當我TAB它創造的4個空格代替tab字符(就像它應該是我猜)

回答

10

不,Haskell縮進不像Python。

Haskell不是關於縮進級別,而是關於使事情與其他事情對齊。

where fib_help n a b 

在這個例子中,你有where,下面標記不是{。這將激活佈局模式(即空白敏感解析)。下一個標記(fib_help)設定下面的塊的起始列:

where fib_help n a b 
--  ^
--  | this is "column 0" for the current block 

的下一行是:

 |n<=1 = b 

所述第一令牌(|)縮進比「列0」以下,這隱式關閉該塊。

您的代碼被解析,如果你寫了

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塊缺少=,並且你不能用|開始新的聲明。

解決方案:在where之後縮進應該成爲where塊的一部分的部分,而不是第一個標記的一部分。例如:

fib n = fib_help n 0 1 
    where fib_help n a b 
      |n<=1 = b 
      |otherwise = fib_help (n-1) b (a+b) 

或者:

fib n = fib_help n 0 1 
    where 
    fib_help n a b 
     |n<=1 = b 
     |otherwise = fib_help (n-1) b (a+b) 

或者:

fib n = fib_help n 0 1 where 
    fib_help n a b 
     |n<=1 = b 
     |otherwise = fib_help (n-1) b (a+b) 
+0

謝謝!現在我懂了! – Schytheron

5

兩件事:

  1. 你需要排隊管道發生輔助功能啓動。 Here is a good explanation on why this is
  2. otherwise仍然需要一個=標誌後(otherwise is synonymous with True):
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) 

要說哈斯克爾縮進像Python的可能是一個以偏概全,僅僅是因爲語言結構是截然不同的。更準確的說法是在Haskell和Python中空白都很重要。

0

否則您錯過了「=」,您可以看到更多示例here。正確的代碼:

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) 
0

你可以想象Haskell和Python的縮進類似,但也有幾個小的差異。

但是,最大的區別在於,Python的縮進敏感語法總是在新行上啓動對齊塊,而Haskell具有帶有對齊塊的語法結構,允許它們通過現有行的一部分開始。這在佈局規則上並沒有什麼不同,但它會顯着影響您對它們的看法(Haskellers不會將規則簡化爲頭腦中的「縮進級別」)。

這裏的一些(可怕)佈局敏感語法的例子在Python:

if True: 
    x = 1 
    while (
not x 
): 
    y = 2 

ifwhile構建體隨後一套對齊語句。字符下一個語句的第一個非空白字符必須縮進比外部塊的對齊位置更遠的某個位置,併爲同一內部塊中的所有後面的語句設置對齊方式。每條語句的第一個字符必須與封閉塊的某個位置對齊(這決定了它是哪個塊的一部分)。

如果我們在對齊位置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部分放在單獨的行上。該inlet語法結構的一部分,但它是由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需要你設置其塊的方式更舒適的思維;在「標題」引入一個塊之後,總是結束該行,然後在下一個「製表位」處開始下一行的塊。