Haskell中的缩进是否像Python一样?

时间:2017-09-01 14:50:55

标签: python parsing haskell syntax-error indentation

我刚开始学习Haskell并且我简要地阅读了一些缩进规则,在我看来,当涉及到缩进时,Haskell就像Python一样(我可能错了)。无论如何,我试着编写一个尾递归的斐波那契函数,并且我一直收到缩进错误,而且我不知道我的代码缩写错误。

错误讯息:

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)

注意:我在Notepad ++中编写代码并且我更改了设置,以便在我创建TAB时创建4个空格而不是制表符(就像我猜的那样)

4 个答案:

答案 0 :(得分: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)

答案 1 :(得分:5)

两件事:

  1. 在辅助函数启动后,您需要将管道排成一行Here is a good explanation on why this is
  2. otherwise后面仍然需要=个符号(otherwise is synonymous with True):
  3. 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)
    

    要说Haskell缩进就像Python一样可能是一种过度概括,仅仅因为语言结构完全不同。更准确的说法就是说空白在Haskell和Python中都很重要。

答案 2 :(得分: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)

答案 3 :(得分:0)

您可以类似地想象Haskell和Python缩进,但存在一些小差异。

然而,最大的区别可能是Python的缩进敏感语法总是在新行上启动对齐块,而Haskell具有带有对齐块的语法结构,允许在现有行的中途开始。这并不是布局规则的真正区别,但它会极大地影响你对它们的看法(Haskellers不会将规则简化为&#34;缩进级别&#34;在他们的头脑中同样多。)

这是Python中一些(可怕的)布局敏感语法的示例:

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

ifwhile结构之后是一系列对齐的语句。字符next语句的第一个非空格必须缩进到比外部块的对齐位置更远的某个位置,并为同一内部块中的所有后续语句设置对齐。每个语句的第一个字符必须与一个封闭块的某个位置对齐(它决定了它所属的块)。

如果我们在位置0对齐时添加z = 3,它将成为全局&#34;块&#34;的一部分。如果我们将它添加到位置4,它将成为if块的一部分。如果我们将它添加到位置5,它将成为while块的一部分。在任何其他位置开始声明将是语法错误。

另请注意,存在多行结构,其对齐完全无关紧要。上面我使用括号在多行上写了while的条件,甚至将行与not x对齐到位置0.即使引入缩进块的冒号在一个上也没关系&#34;未对齐&#34;线;缩进块的相关对齐是while语句的第一个非空白字符(位置4)的位置,以及下一个语句(位置5)的第一个非空白字符的位置。 / p>

这里有一些(可怕的)布局敏感的Haskell:

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本身的定义是&#34; block&#34;的一部分。形成模块的定义,并且必须位于0位。

let块中第一个定义的第一个非空白字符设置块中所有其他定义的对齐方式。在位于第3位的let的外y块中。let的语法在开始缩进块之前不需要换行符(如Python&#39; s缩进构造所有通过结束&#34;标题&#34;用冒号和新行)。内部let块紧跟a = 2之后的let,但a的位置仍设置块(11)中其他定义所需的对齐方式。

同样,您可以分割多条线,这些线不需要对齐线。在Haskell中,您可以使用几乎不是特定布局敏感构造的任何内容来执行此操作,而在Python中,您只能使用括号或使用反斜杠结束行。但是在Haskell中,构成构造的一部分的所有线都必须比它们所属的块进一步缩进。例如,我能够将in a * b定义的z部分放在一个单独的行上。 inlet句法结构的一部分,但它不是 let引入的对齐定义块的一部分,因此它具有没有特别的对齐要求。但是z = ...的整个定义是外部 let定义块的一部分,因此我无法在第3位或更早的位置启动in a * b行;它是一个&#34;延续线&#34;在z定义中,因此需要比该定义的开头缩进。这与Python的延续行不同,后者对它们的缩进没有任何限制。

do还引入了一个对齐的块(&#34;语句&#34;而不是定义)。我可以立即从do开始关注第一个语句,但我选择开始一个新行。这里的块的行为很像Python风格的块;我必须在比外部块(位置3处的外部let的定义)更进一步缩进的某个位置开始它,并且一旦我完成了do块中的所有语句必须对齐到相同的位置(14,这里)。由于pure [10]之后的下一行是z = ...从第3位开始,因此它会隐式结束do块,因为它与let块对齐了#{1}}块。的定义,而不是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)

这使得错误更加突然出现。你没有在where的下一个&#34;缩进级别&#34;中开始定义块。在4个位置,你从第10位开始!然后回到第8位,进入下一个&#34;缩进级别&#34;。

如果你更习惯于思考Python式&#34;缩进级别&#34;比起Haskell风格的对齐,只需按照Python要求你格式化块的方式格式化你的Haskell块;在&#34;标题&#34;之后引入一个块,总是结束该行,然后在下一行&#34; tab stop&#34;开始下一行的块。