循环代码如下
cycle :: [a] -> [a]
cycle [] = errorEmptyList "cycle"
cycle xs = xs' where xs' = xs ++ xs'
我很感激最后一行如何运作的解释。我觉得它会在没有返回的情况下进入无限递归,或者我会说没有在屏幕上打印任何东西。我想我的直觉是错误的。
答案 0 :(得分:4)
一个列表,就像Haskell中的其他所有内容一样,都是懒惰的评估。
粗略地说,要进行OOP类比,您可以将列表视为一种“迭代器对象”。查询时,它报告是否有下一个元素,如果是,那么这个元素是什么,列表的尾部是什么(这是另一个“迭代器对象”)。
定义为
的列表xs = 1 : xs
不会导致非终止。它对应于“迭代器对象”o
,当查询时,它回答:“下一个元素是1
,并且可以使用o
”查询列表的其余部分。基本上,它会自行返回。
这与列表尾部的列表没有区别:列表本身的“指针”:循环列表。这需要一定的空间。
与++
一起添加的内容相同:
xs = [1] ++ xs
与上一个列表相同。
在您的代码中,部分
where xs' = xs ++ xs'
创建一个以xs
开头的列表,然后继续列表本身xs'
。在操作上,它是一个“迭代器对象”o
,一个接一个地返回xs
的元素,当返回xs
的最后一个元素时,它与“你配对可以在o
“查询列表的其余部分。同样,一个后向指针,它构建一种循环列表。
答案 1 :(得分:1)
让我们分别取出最后一行:
cycle xs = xs' where xs' = xs ++ xs'
现在,让我们尝试减少它:
cycle xs = xs ++ (xs ++ (xs ++ (xs ++ ...)))
你可以看到它无限扩展。但请注意,这不是Haskell中表达式减少的方式。当需要时,表达式将减少到WHNF。所以,我们要求cycle
函数中的一些值:
ghci > take 1 $ cycle [1..]
[1]
这是take
函数的实现方式:
take n _ | n <= 0 = []
take _ [] = []
take n (x:xs) = x : take (n-1) xs
现在,由于模式匹配,将首先评估值n
。由于它已经处于正常状态,因此不需要进一步减少,并且将检查它是否小于或等于零。由于条件失败,它将继续进入第二个条件。在这里,将检查它的第二个参数,看它是否等于[]
。像往常一样,haskell会将它评估为WHNF 1:_
。这里_
代表thunk。现在整个表达式将减少到1:take 0 _
。由于此值必须以ghci打印,因此整个1:take 0 _
将再次减少。按照上述类似的步骤,我们会将1:[]
缩减为[1]
。
因此,cycle [1,2,3]
将以(1:xs)
的形式缩减为WHNF,并最终缩减为[1]
。但是如果循环函数本身在它的实现中是严格的,那么它将进入无限循环:
cycle :: NFData a => [a] -> [a]
cycle [] = []
cycle xs = let xs' = xs ++ xs'
in deepseq xs xs'
您可以在ghci中测试:
ghci > take 1 $ cycle [1..]
^CInterrupted.