在阅读“老练的阴谋家”时,我开始了解letrec
我理解它的作用(可以用Y组合词来复制),但这本书使用它来代替在已经define
d的函数上对保持静态的参数进行操作。
使用define
d函数的旧函数在自身上重复出现的示例(无特殊情况):
(define (substitute new old l)
(cond
((null? l) '())
((eq? (car l) old)
(cons new (substitute new old (cdr l))))
(else
(cons (car l) (substitute new old (cdr l))))))
现在举一个使用
letrec
的相同函数的例子:(define (substitute new old l)
(letrec
((replace
(lambda (l)
(cond
((null? l) '())
((eq? (car l) old)
(cons new (replace (cdr l))))
(else
(cons (car l) (replace (cdr l))))))))
(replace lat)))
我不知道为什么他们要重写书中的函数来使用letrec。当以这种方式在静态变量上重复出现时,是否有速度增强,因为你没有一直传递它?是吗?
对于参数保持静态但只有一个参数被减少(例如循环使用列表中的元素)的函数,这是标准做法吗?
一些经验丰富的阴谋家/口齿不清者的意见会有帮助!
最佳答案
所以你有几个答案,涵盖可读性问题,这应该是好的但有一个问题尚不清楚,那就是是否存在任何绩效问题从一个浅显的角度看,letrec
版本,比如命名的-let
版本(实际上是相同的)应该更快,因为循环中传递的参数更少然而,在实践中,编译器可以在背后进行各种优化,比如找出普通版本中的循环未经更改地传递前两个参数,然后将其转换为具有第一个参数的单参数循环。这里有一个plt模块,您可以运行到四个不同的版本,而不是显示特定系统上的数字,您可以轻松地添加更多来尝试其他版本。简短的总结是,在我的机器上,命名的-let
版本稍快一些,使其尾部递归具有更大的总体好处。
#lang scheme
;; original version
(define (substitute1 new old l)
(cond [(null? l) '()]
[(eq? (car l) old) (cons new (substitute1 new old (cdr l)))]
[else (cons (car l) (substitute1 new old (cdr l)))]))
;; letrec version (implicitly through a named-let)
(define (substitute2 new old l)
(let loop ([l l])
(cond [(null? l) '()]
[(eq? (car l) old) (cons new (loop (cdr l)))]
[else (cons (car l) (loop (cdr l)))])))
;; making the code a little more compact
(define (substitute3 new old l)
(let loop ([l l])
(if (null? l)
'()
(cons (let ([fst (car l)]) (if (eq? fst old) new fst))
(loop (cdr l))))))
;; a tail recursive version
(define (substitute4 new old l)
(let loop ([l l] [r '()])
(if (null? l)
(reverse r)
(loop (cdr l)
(cons (let ([fst (car l)]) (if (eq? fst old) new fst)) r)))))
;; tests and timings
(define (rand-list n)
(if (zero? n) '() (cons (random 10) (rand-list (sub1 n)))))
(for ([i (in-range 5)])
(define l (rand-list 10000000))
(define new (random 10))
(define old (random 10))
(define-syntax-rule (run fun)
(begin (printf "~a: " 'fun)
(collect-garbage)
(time (fun new old l))))
;; don't time the first one, since it allocates a new list to use later
(define new-list (substitute1 new old l))
(unless (and (equal? (run substitute1) new-list)
(equal? (run substitute2) new-list)
(equal? (run substitute3) new-list)
(equal? (run substitute4) new-list))
(error "poof"))
(newline))