Haskell中的缩进和Python中的一样吗?

wqsoz72f  于 12个月前  发布在  Python
关注(0)|答案(4)|浏览(176)

我刚开始学习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个空格,而不是一个制表符(就像它应该是我猜)

cpjpxq1n

cpjpxq1n1#

不,Haskell缩进不像Python。
Haskell不是关于缩进级别的,它是关于让事物与其他事物对齐的。

where fib_help n a b

字符串
在这个例子中,你有where,下面的token不是{。这会激活布局模式(即空格敏感解析)。下一个token(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)

n53p2ov0

n53p2ov02#

两件事:
1.你需要在helper函数启动后将管道排成一行。这里有一个很好的解释。

  1. otherwise后面仍然需要一个=符号(otherwiseTrue同义):
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中都很重要。

w6lpcovy

w6lpcovy3#

你错过了一个“=”在否则,你可以看到更多的例子在这里.正确的代码:

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)

字符串

km0tfn4u

km0tfn4u4#

你可以想象Haskell和Python的缩进类似,但有一些小的差异。
然而,最大的区别可能是Python的缩进敏感语法总是在新的一行开始对齐块,而Haskell的语法结构允许对齐块从现有行的一部分开始。这并不是布局规则的真正区别,但它会极大地影响你对它们的看法(Haskellers并不倾向于在他们的头脑中将规则简化到“三层”)。
下面是Python中一些(可怕的)布局敏感语法的例子:

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

字符串
ifwhile结构后面是一套对齐语句。字符next语句的第一个非空格必须缩进到比外部块对齐位置更远的位置,并为同一内部块中的所有后续语句设置对齐方式。每个语句的第一个字符必须与封闭块的某个位置对齐(这决定了它属于哪个块)。
如果我们添加一个z = 3在aligned到位置0,它将是全局“块”的一部分。如果我们添加它aligned到位置4,它将是if块的一部分。如果我们添加它aligned到位置5,它将是while块的一部分。在任何其他位置开始一个语句都将是语法错误。
还要注意的是,有些多行结构的对齐是完全不相关的。上面我用括号在多行上写了while的条件,甚至将not x的行对齐到位置0。甚至引入缩进块的冒号在“未对齐”的行上也没有关系;缩进块的相关对齐是while语句的第一个非空白字符的位置(位置4)和下一个语句的第一个非空白字符的位置(位置5)。
下面是一些(可怕的)布局敏感的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的定义本身是构成模块的定义“块”的一部分,并且需要位于位置0。
let块中第一个定义的第一个非空白字符设置块中所有其他定义的对齐方式。在外部let块中,y位于位置3。但是,let的语法在开始缩进块之前不需要换行符(Python的缩进结构都是以冒号和一个新行结束“header”)。内部的let块在let之后紧跟着a = 2,但是a的位置仍然为块(11)中的其它定义设置所需的对准。
同样,你可以在不需要对齐的情况下将内容拆分为多行。在Haskell中,你可以对几乎任何不是特定布局敏感的结构进行拆分,而在Python中,你只能用括号或用反斜杠结束一行。但在Haskell中,构成结构的所有行都必须比它们所在的块缩进得更深。例如,我可以把z定义的in a * b部分放在单独的一行上。inlet语法结构的一部分,但它 * 不是 * let引入的对齐定义块的一部分,因此它没有特别的对齐要求。然而,z = ...的整个定义是 outerlet定义块的一部分,所以我不能在位置3或更早的位置开始in a * b行;它是z定义的“延续行”,所以需要比该定义的开始处缩进更多。这与Python的延续行不同,后者对缩进没有任何限制。
do还引入了对齐块(“statements”而不是definitions).我本可以从do直接跟随第一条语句,但我选择了开始一个新的行。我不得不从比外块更缩进的位置开始(外部let在位置3的定义),一旦我这样做了,do块中的所有语句都必须对齐到相同的位置由于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)

这使得错误更加突出。你没有在where中的下一个“第三层”4个空格开始定义块,而是在位置10开始!然后回到位置8,进入下一个“第三层”。
如果你更愿意考虑Python风格的“分级”而不是Haskell风格的对齐,那么简单地格式化你的Haskell块,就像Python要求你格式化它的块一样;在“header”引入一个块之后,总是结束一行,然后在下一行的下一个“tab stop”处开始这个块。

相关问题