Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/clojure/3.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
Clojure 迭代产生堆栈溢出错误_Clojure_Stack Overflow_Fibonacci_Frege - Fatal编程技术网

Clojure 迭代产生堆栈溢出错误

Clojure 迭代产生堆栈溢出错误,clojure,stack-overflow,fibonacci,frege,Clojure,Stack Overflow,Fibonacci,Frege,所以我也从弗雷格和哈斯克尔开始。我有使用函数式语言的经验,因为我已经使用Clojure好几年了。 我想尝试的第一件事是我对斐波那契数的惯用方法 next_fib (a, b) = (b, a + b) fibs = map fst $ iterate next_fib (0, 1) fib x = head $ drop x fibs 弗雷格的情况就是这样。它可以工作,但是对于fib的非常高的数字,例如(FIB4000),它会抛出堆栈溢出错误。这让我很惊讶,因为Clojure中的相同函数可以很

所以我也从弗雷格和哈斯克尔开始。我有使用函数式语言的经验,因为我已经使用Clojure好几年了。 我想尝试的第一件事是我对斐波那契数的惯用方法

next_fib (a, b) = (b, a + b)
fibs = map fst $ iterate next_fib (0, 1)
fib x = head $ drop x fibs
弗雷格的情况就是这样。它可以工作,但是对于fib的非常高的数字,例如(FIB4000),它会抛出堆栈溢出错误。这让我很惊讶,因为Clojure中的相同函数可以很好地工作。这是一个Frege bug还是我把整个懒散的评估都搞错了?

你可能不会“把整个懒散的评估都搞错了”,但在这种情况下,你会因为太懒散的评估而被咬两次

尽管GHC在这方面的工作原理与弗雷格完全相同,但结果却不同,似乎对弗雷格不利

但是,Haskell可以通过真正的大thunks(见下文)获得awya,而Frege早期通过堆栈溢出中止的原因是运行时系统管理堆和堆栈的方式。Haskell RTS非常灵活,如果需要的话,可以将大量可用内存用于堆栈。而Frege的运行时系统是JVM,它通常从一个很小的堆栈开始,只足以容纳几百个调用深度。正如您所观察到的,给JVM足够的堆栈空间可以让think正常工作,就像在GHC中一样

由于JVM中的堆栈空间越来越少,我们在Frege中开发了一些技术来避免不必要和不必要的惰性。下面将解释其中两个。最后,在弗雷格,你被迫提前控制懒惰的不良影响,而GHC开发人员可以愉快地编写代码而不必注意

为了理解以下内容,我们需要引入“thunk”的概念。thunk首先是一些尚未计算的表达式。例如,由于元组是惰性的,因此

(b, b+a)
编译为元组构造函数
(,)
b
{a+b}
的应用程序,其中符号
{e}
为了便于讨论,表示thunk的一些依赖于实现的表示,该表示保证在求值时计算表达式
e
。此外,thunk在求值时将其结果存储起来,因此当再次求值同一thunk时,它只返回预计算的结果。(当然,这只能在纯函数式语言中实现。)

例如,在Frege中,为了表示thunks,有一个类
Delayed
,它实现了
Callable
,并安排对结果进行记忆

我们现在将调查结果如何

next_fib (next_fib (0, 1)) 
是。内部应用程序将导致:

(1, {0+1})
然后外部的一个从中计算:

({0+1}, {1+{0+1}})
我们在这里看到,thunk可以嵌套在其他thunk中,这就是这里的问题,因为每个应用程序的
next\fib
都会导致一个元组,它的元素thunk将嵌套在它们内部的上一次迭代的重击

现在考虑当第四千个FIB号的THULK被评估时发生了什么,例如,当你打印它时发生的事情。它必须执行加法,但要添加的数字实际上都是thunk,必须在加法发生之前对其求值。这样,每个嵌套thunk意味着调用该thunks求值方法,除非该thunk已经求值。因此,要打印第4000个数字,我们需要至少4000个堆栈深度,前提是之前没有对该系列的其他thunk进行评估

因此,第一个措施是用严格的元组构造函数替换懒惰的元组构造函数:

(b; a+b)
它不会生成thunks,但会立即计算参数。这在Haskell中不可用,要在Haskell中执行同样的操作,您需要说:

let c = a+b in b `seq` c `seq` (b,c)
但这并不是故事的结局。结果证明,计算仍然溢出堆栈

原因是
iterate
的实现如下所示:

iterate f x = x : iterate f (f x)
这将构建一个无限列表

[ x, f x, f (f x), f (f (f x)), ...]
不用说,除了第一个,所有的术语都是thunks

当按顺序计算列表元素时,这通常不是问题,因为例如,当第三项

{f {f x}}
获取求值,内部thunk已求值并立即返回结果。一般来说,我们只需要足够的堆栈深度就可以达到前面计算的第一个项。这是一个直接来自于try.frege-lang.org上的frege在线回复的演示

frege> next (a,b) = (b; a+b) :: (Integer, Integer)
function next :: (Integer,Integer) -> (Integer,Integer)
frege> fibs = map fst $ iterate next (0,1)
function fibs :: [Integer]
frege> fib = (fibs!!)
function fib :: Int -> Integer
frege> map (length . show . fib) [0,500 ..]
[1,105,209,314,418,523,627,732,836,941,1045,1150,...]
frege> fib 4000
39909473435004422792081248094960912600792...
在这里,使用map,我们强制计算每500个数字(只要REPL需要输出,它将只打印无限列表的初始部分),并计算每个数字的十进制表示的长度(只是为了不显示大的结果数字)。这反过来会强制计算前面的500个数字,但这是可以的,因为有足够的堆栈空间。一旦完成,我们甚至可以计算出fib 4000!因为现在已经评估了6000个Thunk

但是我们可以使用稍微好一点的iterate版本做得更好,它使用head-strict构造函数(!:):

这将立即评估列表的标题,这在我们的案例中是合适的

通过这两个改变,我们得到了一个程序,它的堆栈需求不再依赖于fib的参数。这是证据:

frege> iterate' f x = x !: iterate' f (f x)
function iterate' :: (a->a) -> a -> [a]
frege> fibs2 = map fst $ iterate' next (0,1)
function fibs2 :: [Integer]
frege> (length . show . (fibs2 !!)) 4000
836
frege> (length . show . (fibs2 !!)) 8000
1672
frege> (length . show . (fibs2 !!)) 16000
3344
frege> (length . show . (fibs2 !!)) 32000
6688
frege> (length . show . (fibs2 !!)) 64000
13375
frege> (length . show . (fibs2 !!)) 128000
java.lang.OutOfMemoryError: Java heap space
好的,我们现在需要更多的堆空间来保存超过100000个巨大的数字。但请注意,在最后一步中计算32000个新数字不再存在堆栈问题

我们可以通过一个简单的尾部递归定义消除堆空间问题,该定义不需要标记所有这些数字:

fib :: Int -> Integer
fib n = go n 0 1 where
    go :: Int -> Integer -> Integer -> Integer
    go 0 !a !b = a
    go n !a !b = go (n-1) b (a+b)
我想这比遍历列表还要快

与Clojure中的(?)不同,直接列表访问是O(n),长列表消耗大量空间。因此,如果你需要
fib :: Int -> Integer
fib n = go n 0 1 where
    go :: Int -> Integer -> Integer -> Integer
    go 0 !a !b = a
    go n !a !b = go (n-1) b (a+b)
frege> zzz = arrayFromList $ take 10000 $ map fst $ iterate (\(a,b) -> (b; a+b)) (0n,1)
function zzz :: JArray Integer
frege> elemAt zzz 4000
39909473435004422792081248094960912600792570982820257 ...
yyy = arrayCache f 10000 where 
       f 0 a = 0n
       f 1 a = 1n
       f n a = elemAt a (n-1) + elemAt a (n-2)
fib = elemAt yyy