Scheme 使用定义的过程作为参数会产生与使用lambda表达式作为参数不同的结果

Scheme 使用定义的过程作为参数会产生与使用lambda表达式作为参数不同的结果,scheme,lisp,racket,memoization,r5rs,Scheme,Lisp,Racket,Memoization,R5rs,我想在计划中记下一个程序。代码来自SICP 我的程序fib定义为 (define (fib n) (display "computing fib of ") (display n) (newline) (cond ((= n 0) 0) ((= n 1) 1) (else (+ (fib (- n 1)) (fib (- n 2)))))) 我的回忆录程序如下 (define (memoize f)

我想在计划中记下一个程序。代码来自SICP

我的程序fib定义为

(define (fib n)
    (display "computing fib of ")
    (display n) (newline)
    (cond ((= n 0) 0)
        ((= n 1) 1)
        (else (+ (fib (- n 1))
                (fib (- n 2))))))
我的回忆录程序如下

(define (memoize f)
    (let ((table (make-table)))
        (lambda (x)
            (let ((previously-computed-result (lookup x table)))
                (or previously-computed-result
                    (let ((result (f x)))
                       (insert! x result table)
                       result))))))
让我们定义两个过程

(define mem-fib (memoize fib))
(define mem-fib-lambda (memoize (lambda (n)
             (display "computing fib of ")
             (display n)
             (newline)
             (cond ((= n 0) 0)
               ((= n 1) 1)
                   (else (+ (memo-fib (- n 1))
                            (memo-fib (- n 2))))))))
如您所见,在mem fib中,我使用fib作为参数,但在mem fib lambda中,我使用lambda表达式作为参数,这几乎是相同的

使用5作为参数调用此过程会产生不同的结果,其中第一个结果mem fib将最后一个结果存储在其表中,而mem fib lambda将存储过程中的每个递归计算

(mem-fib 5)
->computing fib of 5
->computing fib of 4
->computing fib of 3
->computing fib of 2
->computing fib of 1
->computing fib of 0
->computing fib of 1
->computing fib of 2
->computing fib of 1
->computing fib of 0
->computing fib of 3
->computing fib of 2
->computing fib of 1
->computing fib of 0
->computing fib of 1
->5
(mem-fib 5)
->5

我的理论是,当我调用mem fib时,fib是在另一个环境中计算的,而mem fib lambda是在它被调用的环境中计算的

为了解决这个问题,我试着在记忆过程中复制一份

(define (memoize proc)
  (define f proc) ;; Here
    (let ((table (make-table)))
        (lambda (x)
            (let ((previously-computed-result (lookup x table)))
                (or previously-computed-result
                    (let ((result (f x)))
                       (insert! x result table)
                       result))))))
这不起作用,所以我试着把它放在let表达式中。据我所知,fib应该是表的同一框架的一部分

(define (memoize proc)
    (let ((table (make-table))
         (f proc)) ;; Here
        (lambda (x)
            (let ((previously-computed-result (lookup x table)))
                (or previously-computed-result
                    (let ((result (f x)))
                       (insert! x result table)
                       result))))))
那也没用

我错过了什么?为什么行为上有差异?我怎样才能得到我想要的结果


谢谢

问题在于,在第一个函数中,您递归调用的是非记忆版本的斐波那契,而不是记忆版本的斐波那契。 解决这个问题的一种方法是这样定义fib:

(define (fib n)
    (display "computing fib of ")
    (display n) (newline)
    (cond ((= n 0) 0)
        ((= n 1) 1)
        (else (+ (mem-fib (- n 1)) ;; Notice we're calling the memoized version here
                (mem-fib (- n 2))))))
(define mem-fib (memoize fib))
可以说,更好的方法是执行以下操作:

(define (fib n)
    (display "computing fib of ")
    (display n) (newline)
    (cond ((= n 0) 0)
        ((= n 1) 1)
        (else (+ (fib (- n 1)) ;; Notice we're calling the NON-memoized version here
                (fib (- n 2))))))
(set! fib (memoize fib)) ;; but we redefine fib to be memoized
这意味着我们只使用了一个名字,它被记忆了。没有什么好方法可以同时提供两个版本,但如果您愿意,这里有一种方法可以做到(如果您想比较性能或其他方面):


这是解决这个问题的另一种方法。这是骗局,不是Scheme,对此我表示歉意(可能是Scheme,但我不知道哈希表在Scheme中是如何工作的)

首先,这里是一个Racket函数,用于在第一个参数上记忆任意数量参数的函数。很明显,为什么我们需要在一瞬间允许额外的争论

(define (memoize f (table (make-hasheqv)))
  ;; Memoize a function on its first argument.
  ;; table, if given, should be a mutable hashtable
  (λ (k . more)
    ;; hash-ref! looks up k in table, and if it is not there
    ;; sets it to be the result of calling the third argument (or
    ;; to the third argument if it's not callable).  thunk just makes
    ;; a function of no arguments.
    (hash-ref! table k
               (thunk (apply f k more)))))
这里有一个诀窍:我们不是将
fib
定义为递归函数,而是将
fib/c
定义为一个非递归函数,它知道如何进行斐波那契数列计算的一步,并将其下注到另一个函数来完成其余的工作。它还告诉您它正在做什么,就像您的函数所做的那样

(define (fib/c n c)
  ;; fib/c does one step of the fibonacci calculation,
  ;; calling c to do the remaining steps.
  (printf "fib of ~A~%" n)
  (if (<= n 2)
      1
      (+ (c (- n 1) c)
         (c (- n 2) c))))
但现在我们可以将其记忆化,并定义一个记忆化版本,
fib/m
(这里您可以看到为什么我需要
memoize
来允许多个参数:我们需要不断向下传递记忆化函数:

(define (fib/m n)
  ;; and here's a memoized fib
  (let ((fib/m (memoize fib/c)))
    (fib/m n fib/m)))
现在(4是它们不同的第一种情况):

在删除打印后:

> (time (fib/u 40))
cpu time: 8025 real time: 7962 gc time: 26
102334155
> (time (fib/m 40))
cpu time: 1 real time: 1 gc time: 0
102334155


请注意,这种方法,即编写一个非递归函数并将其转换为递归函数,与Y的推导方式密切相关(尽管这些方法通常是非常纯粹的,并且坚持只使用一个参数的函数,因此最终使用的是
(λ(f)(λ(n)…(f…)
,而不是
(λ)(n c)…(c…)
。事实证明,这是一个非常有用的技巧。

是的!谢谢!我认为重新定义它是一种方式!它也很有意义。我一整天都在绞尽脑汁!我不能放弃:(
对已经存在的绑定定义
将产生错误。应该使用
(set!fib(memorize fib))
@Sylwester您使用的是什么方案实现?我使用的是guile,它对我很有效。不过,不管怎样,编辑是为了交叉兼容的。我使用的是DrRacket中的
#!r5rs
#!r6rs
,但它与此无关。报告指出更新绑定必须使用
集!
。REPL中的行为可能是错误的与以更高的速度运行程序不同,相同的实现可能会在REPL中正常的情况下失败。
(define (fib/u n)
  ;; unmemoized fib
  (fib/c n fib/c))
(define (fib/m n)
  ;; and here's a memoized fib
  (let ((fib/m (memoize fib/c)))
    (fib/m n fib/m)))
> (fib/u 4)
fib of 4
fib of 3
fib of 2
fib of 1
fib of 2
3
> (fib/m 4)
fib of 4
fib of 3
fib of 2
fib of 1
3
> (time (fib/u 40))
cpu time: 8025 real time: 7962 gc time: 26
102334155
> (time (fib/m 40))
cpu time: 1 real time: 1 gc time: 0
102334155