我想知道何时应该使用制表符,何时应该使用空格?尤其是在警卫方面,我一直在努力,他教你哈斯克尔的一本书,书上说我应该经常使用空间。不过,这本书似乎在定义中使用了4个空格,并配有警卫。例如此功能:
replicate' :: (Num i, Ord i) => i -> a -> [a]
replicate' n x
| n <= 0 = []
| otherwise = x:replicate' (n-1) x
当我用一个空格替换4个空格/制表符时,在otherwise
的情况下会出现缩进/括号缺失错误。然而,如果我使用一个选项卡或4个空格,这是可行的。我是否误解了在制表符上使用空格?每次应该是4个空格吗?因为通常1个空间确实有效,只是有了警卫,ghci几乎总是(令人愤怒的是,并不总是)在这里抱怨。
我正在使用崇高的btw,以防出现问题。
提前非常感谢。
例如:
maximum' [] = error "maximum of empty list"
maximum' [x] = x
maximum' (x:xs)
| x > maxTail = x
| otherwise = maxTail
where maxTail = maximum' xs
抛出缩进错误
听起来OP的实际问题在特定文件中是一个奇怪的状态,但我想我应该在这里提供一般问题的答案。
Haskell语法的大多数部分对缩进完全不敏感(关于布局Haskell代码的大多数常见做法都是风格化的,而不是必要的)。例如,在OP的例子中,所有这些写最后一个方程的方法都很好:
-- guards indented more than where
maximum' (x:xs)
| x > maxTail = x
| otherwise = maxTail
where maxTail = maximum' xs
-- where indented more than guards
maximum' (x:xs)
| x > maxTail = x
| otherwise = maxTail
where maxTail = maximum' xs
-- all on one line, no indentation at all!
maximum' (x:xs) | x > maxTail = x | otherwise = maxTail where maxTail = maximum' xs
甚至像这样可怕的东西也能起作用:
-- please, no
maximum' (x:xs) |
x
> maxTail = x
| otherwise
= maxTail where
maxTail
= maximum' xs
在显示的代码中,有两件事可以把缩进搞砸:
- 使用多行定义公式,其中任何连续行都不以至少一个空白字符开头
- 使用多行定义
where
子句,其中任何连续行的起始字符位置都小于where
关键字后第一个字符的位置(即maxTail
中的m
)
否则,本例中的空格根本无关紧要(除了分隔标识符和关键字之外)。
在Haskell中,缩进的重要性基本上只有一种通用方式。实际上,重要的不是缩进,而是对齐。这发生在";块";包含可变数量的条目:
let <decls> in <expr>
表达式在<decls>
部分中包含1个或多个声明where
子句引入一个或多个声明instance
定义的where
部分具有零个或多个方法定义do
块具有1个或多个语句case
表达具有1个或多个情况(具有EmptyCase
扩展的情况为零或更多)- 等等
这些块中的可变条目数是对齐重要的位置。总是有一个关键字介绍块,块中第一个条目的字符位置设置对齐方式;在此之后,从该字符位置开始的每一行都被视为块中下一个条目的开始,从开始并超过该位置的每一行都被视为上一个条目的延续行,并且在对齐位置之前开始的第一行被视为该块的结束(并且该行的内容不是块的一部分)。有时,还有一个关键字将指示块的结束,而与任何缩进无关(例如,let <decls> in <expr>
的in
部分由in
关键字指示,即使它与<decls>
的一部分或全部在同一行上)。
顺便说一句,在这一点上,你可能想知道为什么像这样的代码可能会出现错误:
bar x y
= x + y
我没有使用上面的任何where
、let
等块,但通过在没有缩进的情况下将bar
定义继续到新行上,可能会在这里出现对齐错误?我不是承诺缩进只在块中重要吗?实际上,模块的整个全局范围是一个对齐的块!我们通常不会注意到这一点,因为对于这个块,使用对齐位置0是惯例。但从技术上讲,这就是发生的事情(因此,全局块中从对齐0开始的声明之一不能有一个延续行)。
这种基于对齐而非缩进的布局是选项卡通常被认为难以用于布局Haskell代码的原因。举个例子,考虑一下:
foo x y z = xy + yz
where xy = x * y
yz = y * z
在这里,我使用了4个空格来缩进where
部分,这是空白完全不相关的地方之一,所以我可以使用任何我喜欢的东西。因此,如果我习惯于在其他编程语言中使用制表符作为缩进,我可能会倾向于使用制表符而不是4个空格。
事情变得令人讨厌的是CCD_ 22行的正确缩进不是"0";在";,而是";与CCD_ 23定义完全一致";。因此,如果我使用了一个制表符来缩进where
,那么唯一缩进下一行的正确方法是使用一个后面跟着6个空格的制表符。根据我的经验,这是即使是聪明的格式化代码编辑器也永远不会做对的事情(更不用说人工操作了);如果我的视图设置中有一个选项卡占用的空间小于where
关键字(例如常见的4个空格),那么我可能会得到至少2个前导选项卡,然后是足够的空格,使yz = y * z
行出现,以符合上面的定义。
根据规范,Haskell编译器将制表符视为间隔八个空格。因此,我在上面描述的情况(其中where
中的第一个定义是在1个选项卡加上6个正常字符,第二个是在2个选项卡加2个正常字符)导致了一个不可见的错误。编译器认为这些定义位于位置14和18,但对我来说,它们看起来是一样的。这类问题不好玩。因此,投了赞成票的评论";何时使用选项卡?从不这很容易">
从技术上讲,您可以将编辑器设置为在8个空格处显示制表符,然后给定的缩进量是所有空格还是制表符和空格的任何组合看起来都一样都无关紧要。然而,大多数用户不喜欢将他们的编辑器设置为将选项卡显示为8个空格,并且固定任何特定的数字都会破坏使用选项卡缩进的整个要点(每个用户都可以在编辑器中独立配置"缩进级别"的视觉外观)。
也可以采用避免该问题的代码样式。基本上:总是在关键字引入块后立即结束该行,这样块就从新行开始(您可以提高下一个缩进级别)。然后你会写(以OP为例):
maximum' (x:xs)
| x > maxTail = x
| otherwise = maxTail
where
maxTail = maximum' xs
如果你这样做,那么你的对齐位置将始终是精确数量的制表符和零个正常字符,这样你就不会被迫使用制表符和空格混合的前导空格。事实上,如果您这样编码,Haskell的对齐规则将与Python的缩进规则极为相似(它们不同的主要原因是Haskell允许您在与前一代码相同的行上开始对齐的块,而Python的块前面总是有一行以冒号结尾的)。
但是到目前为止,在Haskell中使用制表符最常见的方法是:简单地不要这样做;"止动片";当你按下tab键时,如果你愿意的话。但是要确保物理源代码文件是用空格保存的。
免费的肥皂盒时间!
就我个人而言,上面的原因就是为什么我不喜欢在任何语言中使用选项卡。因为迟早会有人想让一行上的东西与另一行上上的东西在视觉上对齐,而这通常需要由制表符和空格混合而成的缩进。制表符和空格的混合几乎从来没有被编辑器正确处理过(要做到这一点,通常需要编辑器能够在编码器完成键入之前判断一行何时是续行或新语法结构的开始,这在最好的情况下取决于语言,在最坏的情况下是不可能的)。因此,一旦有人使用了与他们使用的不同的选项卡宽度偏好,他们就会编写格式错误的代码。
一个例子是这种相当常见的布局(没有特定的语言):
class Foo {
public int foo(int x, char y, long listOfParameters,
bool z, double ooopsRanOutOfLetters) {
codeStartsHere();
如果缩进级别是制表符,那么参数列表的正确缩进是1制表符和15个空格,但有些人可能会得到4制表符和3个空格,这会在任何其他制表符宽度设置下完全取消对齐。基本上,如果要为每个编码器的偏好配置缩进级别(通过设置制表符宽度),则插入缩进级别和插入视觉上相等数量的空格之间存在根本区别,需要您在每次按下制表键时考虑您想要的是哪种。即使格式化纯粹是对人类读者的视觉帮助,并且不会改变编译器/解释器读取代码的方式,但可以说,帮助人类读者比仅仅编写机器可以接受的东西更重要。
同样,这个问题可以通过严格遵守精心构建的风格指南来解决,以避免出现上述布局。但我只是不想在设计或评估风格指南时,也不想在编写代码时考虑这些"总是用空格缩进";这是一个非常简单的规则,可以放在风格指南中,无论你采用了什么其他规则(无论这些其他规则是否得到严格遵守或有例外),这都不是一个问题。
只使用空格,因为Haskell对缩进敏感,并且隐式布局块必须从大于其布局关键字的列开始,因此准确跟踪列很重要。
此外,根据Haskell2010,制表位间隔8列,按照目前最多4个空格的缩进标准,这是一个巨大的数字。