Memory 循环函数的意外内存使用

Memory 循环函数的意外内存使用,memory,haskell,cycle,Memory,Haskell,Cycle,在下面的程序中,我只希望cycle3以恒定内存运行(它明确地连接了节点)。然而,cycle2也在恒定内存中运行,原因我无法理解。我希望cycle2做与cycle1完全相同的工作,因为 xs' = xs ++ xs' xs' = xs ++ xs ++ xs' -- substitute value of xs' xs' = xs ++ xs ++ xs ++ xs' -- substitute value of xs' again xs' = xs ++ xs ++ xs ++ ... -- a

在下面的程序中,我只希望
cycle3
以恒定内存运行(它明确地连接了节点)。然而,
cycle2
也在恒定内存中运行,原因我无法理解。我希望
cycle2
做与
cycle1
完全相同的工作,因为

xs' = xs ++ xs'
xs' = xs ++ xs ++ xs' -- substitute value of xs'
xs' = xs ++ xs ++ xs ++ xs' -- substitute value of xs' again
xs' = xs ++ xs ++ xs ++ ... -- and so on

有人能解释一下我遗漏了什么吗


modulemain其中
导入System.Environment(getArgs)
循环1::[a]->[a]
cycle1[]=错误“空列表”
cycle1 xs=xs++cycle1 xs
循环2::[a]->[a]
cycle2[]=错误“空列表”
cycle2 xs=xs'其中xs'=xs++xs'
循环3::[a]->[a]
cycle3[]=错误“空列表”
cycle3 xs=let
xs'=go xs'xs
在xs'
哪里
go::[a]->[a]->[a]
开始[最后]=最后:开始
开始(x:xs)=x:开始xs
testMem::(显示a)=>([a]->[a])->[a]->IO()
testMem f xs=print(xs',xs')--只打印第一个val,需要第二个val保存到引用中
哪里
xs'=f xs
main::IO()
main=do
args只是循环1
[“2”]->只需循环2次即可
[“3”]->只需循环3次即可
_->没有
McCyclefunc案
只需cycleFunc->testMem cycleFunc[0..8]
Nothing->putStrLn“有效的参数是{1,2,3}中的一个。”

cycle1
每次使用循环时都会创建一个新列表。原因应该显而易见


然而,
cycle2
并不能做到这一点。它创建一个变量
xs'
,该变量在它自己的定义中使用。在
cycle1
中,它必须在每次使用
xs
时重新评估
cycle1
函数,但在
cycle2
中,它没有任何递归函数。它只引用同一个变量,该变量已经有一个已知值。

它归结为共享或不共享相等的thunk。两个相等的thunk是保证产生相同结果的thunk。在
cycle1
的情况下,每次点击
xs
末尾的
[]
,您都会为
cycle1 xs
创建一个新的thunk。需要为该thunk分配新内存,并且需要从头开始计算其值,这将在您遍历它时分配新的列表对

如果将
xs'
重命名为
result
,我认为
cycle2
避免这种情况的方法会更容易理解(并且我删除了“error on
[]
”案例):

这个定义在语义上等同于
cycle1
(对相同的参数产生相同的结果),但是理解内存使用的关键是从创建thunk的角度来看它。当您执行此函数的编译代码时,它所做的就是立即为
结果创建一个thunk。您可以将thunk视为一种可变类型,大致如下所示(全部由伪代码组成):

这是一个包含指向所需值的thunk的指针加上指向强制这些thunk的代码的指针的记录,或者只是计算结果。在
cycle2
的情况下,
result
的thunk指向
(++
的目标代码,
xs
result
的thunk。最后一位意味着
结果的thunk有一个指向自身的指针,这解释了常量空间行为;强制执行
结果的最后一步是使其指向自身

另一方面,在
循环1
的情况下,thunk具有
(++)
的代码、
xs
的thunk以及从头开始计算
循环1 xs
的新thunk。原则上,编译器可以识别对后一个thunk的引用可以替换为对“父”块的引用,但编译器不会这样做;而在
cycle2
中,它只能这样做(一个变量的一个实例化绑定=一个块)

请注意,这种自参考thunk行为可以分解为
fix
的适当实现:

-- | Find the least fixed point of @f@.  This implementation should produce
-- self-referential thunks, and thus run in constant space.
fix :: (a -> a) -> a
fix f = result
    where result = f result

cycle4 :: [a] -> [a]
cycle4 xs = fix (xs++)
cycle2 :: [a] -> [a]
cycle2 xs = result 
    where result = xs ++ result
type Thunk a = union { NotDone (ThunkData a), Done a }
type ThunkData a = struct { force :: t0 -> ... -> tn -> a
                          , subthunk0 :: t0
                          , ...
                          , subthunkn :: tn }
-- | Find the least fixed point of @f@.  This implementation should produce
-- self-referential thunks, and thus run in constant space.
fix :: (a -> a) -> a
fix f = result
    where result = f result

cycle4 :: [a] -> [a]
cycle4 xs = fix (xs++)