Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/10.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 GHC测试套件中的这种简短记忆功能是如何工作的?_Haskell_Memoization - Fatal编程技术网

Haskell GHC测试套件中的这种简短记忆功能是如何工作的?

Haskell GHC测试套件中的这种简短记忆功能是如何工作的?,haskell,memoization,Haskell,Memoization,是以下记忆功能的完整可运行代码: memo f = g where fz = f Z fs = memo (f . S) g Z = fz g (S n) = fs n -- It is a BAD BUG to inline 'fs' inside g -- and that happened in 6.4.1, resulting in exponential behaviour -- memo f = g (f Z) (mem

是以下记忆功能的完整可运行代码:

memo f = g
  where
    fz = f Z
    fs = memo (f . S) 
    g  Z    = fz
    g (S n) = fs n
    -- It is a BAD BUG to inline 'fs' inside g
    -- and that happened in 6.4.1, resulting in exponential behaviour

-- memo f = g (f Z) (memo (f . S))
--        = g (f Z) (g (f (S Z)) (memo (f . S . S)))
--        = g (f Z) (g (f (S Z)) (g (f (S (S Z))) (memo (f . S . S . S))))

fib' :: Nat -> Integer
fib'             =  memo fib
  where
  fib Z          =  0
  fib (S Z)      =  1
  fib (S (S n)) = fib' (S n) + fib' n

我试着用手来展开这些项,但那个展开看起来就像是缓慢的,未变形的函数。它是如何工作的?注释掉的代码是如何派生出来的?

这很难解释。我将从一个更简单的例子开始

人们必须记住两者之间的区别

\x -> let fz = f 0 in if x==0 then fz else f x
let fz = f 0 in \x -> if x==0 then fz else f x
两者都计算相同的函数。但是,当使用参数
0
调用前者时,前者将始终(重新)计算
f0
。相反,后者将仅在第一次使用参数调用时计算
f0
——当这种情况发生时,计算
fz
,并将结果永久存储在那里,以便下次需要
fz
时可以再次使用

这和我的想法没有太大区别

f 0 + f 0
let fz = f 0 in fz + fz
其中后者将只调用一次
f0
,因为第二次
fz
将被计算

因此,我们可以实现
f
的轻度记忆,只存储
f0
,如下所示:

g = let fz = f 0 in \x -> if x==0 then fz else f x
相当于:

g = \x -> if x==0 then fz else f x
   where
   fz = f 0       
g = g' 
   where
   fz = f 0       
   g' = \x -> if x==0 then fz else f x
g = g' 
   where
   fz = f 0       
   g' x = if x==0 then fz else f x
g = g' 
   where
   fz = f 0       
   g' 0 = fz
   g' x = f x
请注意,在这里我们不能将
\x->
放在
=
的左侧,否则我们将失去记忆功能

相当于:

g = \x -> if x==0 then fz else f x
   where
   fz = f 0       
g = g' 
   where
   fz = f 0       
   g' = \x -> if x==0 then fz else f x
g = g' 
   where
   fz = f 0       
   g' x = if x==0 then fz else f x
g = g' 
   where
   fz = f 0       
   g' 0 = fz
   g' x = f x
现在我们可以毫无问题地将
\x->
带到左侧

相当于:

g = \x -> if x==0 then fz else f x
   where
   fz = f 0       
g = g' 
   where
   fz = f 0       
   g' = \x -> if x==0 then fz else f x
g = g' 
   where
   fz = f 0       
   g' x = if x==0 then fz else f x
g = g' 
   where
   fz = f 0       
   g' 0 = fz
   g' x = f x
相当于:

g = \x -> if x==0 then fz else f x
   where
   fz = f 0       
g = g' 
   where
   fz = f 0       
   g' = \x -> if x==0 then fz else f x
g = g' 
   where
   fz = f 0       
   g' x = if x==0 then fz else f x
g = g' 
   where
   fz = f 0       
   g' 0 = fz
   g' x = f x
现在,它只记忆
f0
,而不是每个
fn
。实际上,两次计算
g4
将导致
f4
计算两次

为了避免这种情况,我们可以开始使用
g
来处理任何函数
f
,而不是固定函数:

g f = g'    -- f is now a parameter
   where
   fz = f 0       
   g' 0 = fz
   g' x = f x
现在,我们利用这一点:

-- for any f, x
g f x = f x
-- hence, in particular
g (f . succ) (pred x) = (f . succ) (pred x) = f (succ (pred x)) = f x
因此,
g(f.succ)(pred x)
是一种复杂的书写方式。与往常一样,
g
将函数记忆为零。但是这是
(f.succ)0=f1
。通过这种方式,我们在
1
获得了回忆录

因此,我们可以递归

g f = g'    -- f is now a parameter
   where
   fz = f 0       
   g' 0 = fz
   g' x = g (f . succ) (pred x)
如果使用
0
调用,则使用
fz
存储
f0
,并将其存储

如果使用
1
调用,这将调用
g(f.succ)
,这将为
1
案例分配另一个
fz
。 这看起来不错,但是
fz
不会持续很长时间,因为每次调用
g'x
时都会重新分配它,从而取消记忆

为了解决这个问题,我们使用另一个变量,这样
g(f.succ)
最多只计算一次

g f = g'    -- f is now a parameter
   where
   fz = f 0       
   fs = g (f . succ)
   g' 0 = fz
   g' x = fs (pred x)
这里,
fs
最多计算一次,并将导致为
1
情况分配另一个
fz
。这个
fz
现在不会消失


递归地,我们可以确信,现在所有的值
fn
都被记录下来了。

有点整洁
g
是一个闭包,包含
f Z
memo(f.S)
的thunk,这本身就是不同
f
g
版本。因此,您将得到一个列表形状的闭包链,每个闭包都包含一个thunk for
fn
。我认为您可以忽略注释掉的代码。它似乎与给定的
memo
的定义不匹配,因为如果您尝试键入
g(f Z)
,您会发现它具有type
Nat
,因此无法应用于参数
(memo(f.S))
。可能它指的是一个较旧版本的
备忘录
,也可能作者只是“大声思考”并留下了评论。非常有趣的是,看到这么一个简短、简单的函数花了多少心思!与其他一些记忆函数不同,这个函数确实也适用于正常整数,如示例中所示,而不仅仅适用于惰性NAT等。