Haskell中的范围(GHCi)

43
我正在阅读Learn You A Haskell for Great Good. 他的例子[2,2..20][3, 6..20]运行良好,但我得到了三个奇怪的结果:
  1. Count by 17's from one to 171: [17, 1..171] yields the null list.
  2. Count by 17's from seventeen to 1711111: [17, 17..171111] repeats the number 17 until I interrupt GHCi.
  3. There is a weird difference between take 54 [171, 234..] and take 54 [171, 244..]:

     ghci> take 54 [171, 234..]
    [171,234,297,360,423,486,549,612,675,738,801,864,927,990,1053,1116,1179,1242,1305,1368,1431,1494,1557,1620,1683,1746,1809,1872,1935,1998,2061,2124,2187,2250,2313,2376,2439,2502,2565,2628,2691,2754,2817,2880,2943,3006,3069,3132,3195,3258,3321,3384,3447,3510]
    
     ghci> take 54 [171, 244..]
    [171,244,317,390,463,536,609,682,755,828,901,974,1047,1120,1193,1266,1339,1412,1485,1558,1631,1704,1777,1850,1923,1996,2069,2142,2215,2288,2361,2434,2507,2580,2653,2726,2799,2872,2945,3018,3091,3164,3237,3310,3383,3456,3529,3602,3675,3748,3821,3894,3967,4040]
    
为什么?
4个回答

72
你对范围的含义有些偏差。Haskell 的范围语法有四种情况: [first..], [first,second..], [first..last], [first,second..last]Learn You A Haskell 中的例子如下:
ghci> [2,4..20]  
[2,4,6,8,10,12,14,16,18,20]  
ghci> [3,6..20]  
[3,6,9,12,15,18]   

请注意,在第一种情况下,列表以两个为单位计数,在第二种情况下,列表以三个为单位计数。这是因为第一项和第二项之间的差别分别为二和三。在您的语法中,您尝试编写[first,step..last]来获取列表[first,first+step,first+2*step,...,last]; 然而,像这样的范围步长实际上是前两个数字之间的差异。如果没有第二个元素,则步长始终为一;如果没有最后一个元素,则列表会无限延伸(或直到达到类型的最大/最小元素)。因此,让我们看看您的三个示例:
  • [17,1..171] == []。由于你指定了17,1,Haskell看到你的列表的前两个元素应该是十七和一,因此你必须按照-16进行计数。在这种情况下,Haskell希望在元素比最后一个元素小的时候停止——但它们从一开始就这样,因此不会产生任何元素。要按一进行计数,你需要使用[17,18..171](你的列表的前两个元素是17和18),或者只需使用[17..171]

  • [17, 17..171111] == repeat 17。这个有趣。由于你的列表的前两个元素都是17,Haskell确定你必须按零递增——并且它将愉快地继续递增,直到结果超过171111。当然,在按零递增时,这永远不会发生,因此你会得到一个无限的十七列表。要按十七递增,你需要使用[17,34..171111],或者如果你认为更清晰,可以使用[17,17+17..171111]

  • take 54 [171,234..]take 54 [171,244..]。我不确定你期望的行为是什么,但它们每个都与上述相同:第一个返回一个从171开始,按234 - 171 = 63计数的五十四个整数列表;第二个返回一个从171开始,按244 - 171 = 73计数的五十四个整数列表。每个列表无限延伸(或者至少直到maxBound,如果列表是有限的Ints而不是任意大的Integers),因此你只需请求前五十四个元素。

如果您想了解有关范围语法的更多细节(它被转换为 Enum 类型类中的函数),包括浮点数范围的略微令人惊讶的行为,hammar在另一个问题上有很好的回答


我认为原帖作者期望在 [a, b .. c] 中,值 a 是以 b 开始的范围中的步长。(正如你所指出的那样,实际上步长是 b - a)。 - wstomv

13

这些操作的语义与您期望的有点不同。构造格式[a,b..c]实际上只是enumFromThenTo a b c的语法糖,其行为类似于:

计算d = b - a。输出[a,a+d,a+d+d,a+d+d+d,...]。重复该过程,直到达到条件a+n*d > c,如果dc-a的符号不同(此时列表将是无限长的,则没有输出),或者达到maxBoundminBound,然后结束输出。(当然,由于我们在此处使用任意的Enum实例,因此实现方式可能会有所不同。)

因此,[1,3..10]变成了[1,3,5,7,9],并且由于17-17=0[17, 17..171111]产生[17,17+0,17+0+0...]。根据这个稍微复杂的规则,[17,1..171]得到空列表。

此外,[x,y..]是使用函数enumFromThen x y实现的,它的行为就像enumFromThenTo一样,只是没有边界条件,因此如果您的Enum是无限的,则结果列表也将是无限的。


4

我也对这种行为感到有些惊讶,因此我编写了一个范围函数,它对我来说(也可能对你来说)更加自然:

range step start end = takeWhile (<=end) $ iterate (+step) start

举个例子:

从1到171以17为步长计数

使用range 17 1 171,会产生[1,18,35,52,69,86,103,120,137,154,171]

从17到1711111以17为步长计数

使用range 17 17 1711111,会产生[17,34,51,68,85, ...


2

我对这个教程也感到困惑:教程使用了单词“步骤”,但没有解释,而且在我看来不是我所理解的步骤。然后它展示了一个例子,容易被误解,因为[2,4..20]看起来像是从4开始以步长2递增。

然而,关键在于输出结果:

ghci> [2,4..20]  
[2,4,6,8,10,12,14,16,18,20]

如果您仔细观察(我没有),它的意思是从2开始,下一个是4,隐含步长为(4-2),以步长为2输出数字,最多到20。

"ghci>" [1,6..20]
[1,6,11,16]

由于16 + 5大于20,因此不会输出Note 20。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接