Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/9.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
haskell中的严格执行问题_Haskell_Lazy Evaluation_Strict - Fatal编程技术网

haskell中的严格执行问题

haskell中的严格执行问题,haskell,lazy-evaluation,strict,Haskell,Lazy Evaluation,Strict,如果要假装Haskell是严格的,并且我有一个不利用惰性的算法(例如,它不使用无限列表),那么如果我只使用严格的数据类型并注释我使用的任何函数,使其参数严格,会出现什么问题?是否会有绩效处罚,如果会,处罚程度如何;还会出现更糟糕的问题吗?我知道无意识地对每个函数和数据类型严格是肮脏的、毫无意义的和丑陋的,我不打算在实践中这样做,但我只想了解,通过这样做,Haskell是否默认变得严格 第二,如果我淡化偏执,只对数据结构进行严格控制:只有在使用某种形式的积累时,我才会担心懒惰的实现带来的空间泄漏吗

如果要假装Haskell是严格的,并且我有一个不利用惰性的算法(例如,它不使用无限列表),那么如果我只使用严格的数据类型并注释我使用的任何函数,使其参数严格,会出现什么问题?是否会有绩效处罚,如果会,处罚程度如何;还会出现更糟糕的问题吗?我知道无意识地对每个函数和数据类型严格是肮脏的、毫无意义的和丑陋的,我不打算在实践中这样做,但我只想了解,通过这样做,Haskell是否默认变得严格

第二,如果我淡化偏执,只对数据结构进行严格控制:只有在使用某种形式的积累时,我才会担心懒惰的实现带来的空间泄漏吗?换句话说,假设该算法在严格的语言中不会出现空间泄漏。还假设我在Haskell中仅使用严格的数据结构实现它,但小心地使用seq来计算递归中传递的任何变量,或者使用内部小心地执行此操作的函数(如fold'),我会避免任何空间泄漏吗?请记住,我假设在严格的语言中,相同的算法不会导致空间泄漏。因此,这是一个关于懒惰和严格之间的实现差异的问题

我问第二个问题的原因是,除了试图通过使用惰性数据结构或严格的数据结构来利用惰性之外,我迄今为止看到的所有空间泄漏示例,仅涉及在累加器中开发thunks,因为它不是递归调用的函数,所以在将自身应用于累加器之前没有对其求值。我知道,如果一个人想利用懒惰,那么他就必须格外小心,但在一种严格的默认语言中也需要小心

谢谢。

懒惰加速了事情的发展 你的情况可能会更糟。
++
的简单定义是:

xs ++ ys = case xs of (x:xs) -> x : (xs ++ ys)
                      []     -> ys
懒惰使这成为O(1),尽管它也可能添加O(1)处理以提取缺点。如果没有惰性,则需要立即评估
++
,从而导致O(n)操作。(如果你从未见过O(.)符号,这是计算机科学从工程师那里偷来的东西:给定一个函数
f
,集合
O(f(n))
是所有算法的集合,这些算法在最坏的情况下最终与
f(n)
成比例,其中
n
是输入函数的位数。[形式上,存在一个
k
N
,因此对于所有
N>N
算法所花费的时间小于
k*f(N)
],所以我是说懒惰使得上述操作
O(1)
或最终为常数时间,但每次提取都会增加一个常数开销,而严格性使操作在列表元素的数量上成为线性,假设这些元素具有固定的大小

这里有一些实际的例子,但O(1)增加的处理时间可能也会“叠加”成O(n)依赖项,因此最明显的例子是O(n2)双向。这些例子中仍然可能存在差异。例如,一种工作不好的情况是使用堆栈(后进先出,这是Haskell列表的样式)用于队列(先进先出)

这里有一个由严格的左折叠组成的快速库;我使用了case语句,这样每一行都可以粘贴到GHCi中(使用
let
):

这里的技巧是
queue
sl_queue
都遵循
xs++[x]
模式将一个元素附加到列表的末尾,这将获取一个列表并建立该列表的精确副本。然后,GHCi可以运行一些简单的测试。首先,我们制作两个项目,并强制它们发出响声,以证明此操作本身非常快,并且在内存中不会太昂贵:

*Main> :set +s
*Main> let vec = test 10000; slvec = sl_test 10000
(0.02 secs, 0 bytes)
*Main> [foldl' (+) 0 vec, slfoldl' (+) 0 slvec]
[50005000,50005000]
(0.02 secs, 8604632 bytes)
现在我们进行实际测试:对队列版本求和:

*Main> slfoldl' (+) 0 $ sl_queue slvec
50005000
(22.67 secs, 13427484144 bytes)
*Main> foldl' (+) 0 $ queue vec
50005000
(1.90 secs, 4442813784 bytes)
请注意,这两个版本在内存性能方面都很糟糕(list append stuff仍然是秘密的O(n2)),它们最终占用了千兆字节的空间,但严格版本占用的空间是后者的三倍,占用的时间是后者的十倍

有时数据结构应该改变 如果你真的想要一个严格的队列,有两个选项。一个是指状树,如
数据。顺序
——它们做事情的
视图
方式有点复杂,但可以获得最右边的元素。然而,这有点重,一个常见的解决方案是O(1)摊销:定义结构

data Queue x = Queue !(SL x) !(SL x)
其中,
SL
术语是上面的严格堆栈。定义一个严格的
reverse
,我们称之为
slreverse
,这是显而易见的方法,然后考虑:

enqueue :: Queue x -> x -> Queue x
enqueue (Queue xs ys) el = Queue xs (Cons el ys)

dequeue :: Queue x -> Maybe (x, Queue x)
dequeue (Queue Nil Nil) = Nothing
dequeue (Queue Nil (Cons x xs)) = Just (x, Queue (slreverse xs) Nil)
dequeue (Queue (Cons x xs ys)) = Just (x, Queue xs ys)
这是“摊销O(1)”:每次
出列
颠倒列表,为一些
k
花费O(k)个步骤,我们确保我们创建的结构不必为
k
更多步骤支付这些成本

懒惰隐藏错误 另一个有趣的点来自data/codata的区别,其中数据是由子单元上的递归遍历的有限结构(即,每个数据表达式都停止)codata是结构的其余部分——严格列表和流。事实证明,当你正确地进行区分时,严格数据和惰性数据之间没有形式上的区别——严格数据和惰性数据之间唯一的形式上的区别是它们如何处理自身中无限循环的术语:严格将探索循环和因此也将无限循环,而lazy将简单地将无限循环向前传递,而不会下降到其中

同样地,您会发现
x=slhead(Cons x undefined)
将在
head(x:undefined)
成功的地方失败。因此您可以“发现”隐藏的infine
enqueue :: Queue x -> x -> Queue x
enqueue (Queue xs ys) el = Queue xs (Cons el ys)

dequeue :: Queue x -> Maybe (x, Queue x)
dequeue (Queue Nil Nil) = Nothing
dequeue (Queue Nil (Cons x xs)) = Just (x, Queue (slreverse xs) Nil)
dequeue (Queue (Cons x xs ys)) = Just (x, Queue xs ys)