不应滥用named let

> (define (f x) x)
> (define (g x) (let rec((x x)) x))
> (define a '(1 2 3))

> (f a)
(1 2 3)
> (eq? a (f a))
#t
> (eq? a (g a))
#t

> (define b (g a))
> (set-car! b 10)
> a
(10 2 3)
> 

可见,g函数的定义中,named let并未深拷贝x的值,它只是建立传入参数的引用而已.

那这也说明,如果一个函数所有参数在递归过程中都会发生改变,那么一般没必要用named let.

又如:

;切割named let版 (1 2 3 4) 2 -> (3 4) 
(define (tail x n)
  (let recur ((x x)(n n))
    (if (null? x)
        '()
        (if (> n 0)
            (recur (cdr x) (- n 1))
            (cons (car x) (recur (cdr x) (- n 1)))))))

;切割原始版
(define (tail x n)
  (if (null? x)
      '()
      (if (> n 0)
          (tail (cdr x) (- n 1))
          (cons (car x) (tail (cdr x) (- n 1))))))

 原始版就是更好的.

但是,对于递归过程中函数所有参数都在变化的情形,有两种情况例外,仍然需要named let:

(1)函数要求返回递归过程中的某些变量的最终状态.例如求一个list的元素个数的函数定义:

(define (len x)
  (let recur ((x x)(y 0))
    (if (null? x)
        y
        (recur (cdr x) (+ y 1)))))

由于需要一个变量来记录(cdr x)的次数,因此在内嵌函数recur中增加一个参数y来实现是非常合适的.

(2)处理不定参数的情形.这个可以用map来作非常好的说明:

(define (imap f x . y)
  (if (null? y) 
      (let recur ((x x)) 
        (if (null? x) 
            '() 
            (cons (f (car x)) (recur (cdr x))))) 
      (let recur ((x x) (y y)) 
        (if (null? x) 
            '() 
            (cons (apply f (car x) (imap car y)) (recur (cdr x) (imap cdr y))))))) 

显然,结合条件判断,named let能够将复杂情形转化为简单情形.思路是:

如果y是空表,那么imap等于一个只有1个参数recur函数.否则就等于2个参数的recur函数.而后者的参数传递过程中,我们又需要用到1个参数的情形:

(imap car y)
(imap cdr y)

这真是非常精妙的.

原文地址:https://www.cnblogs.com/xiangnan/p/3392293.html