我正在阅读Friedman 和 Felleisen的 The Seasoned Schemer,但我对他们的一些最佳实践感到有些不安。作者特别推荐:
- 用于
letrec
删除对于递归应用程序不会更改的参数; - 用于
letrec
隐藏和保护功能; - 用于
letcc
突然而迅速地返回值。
让我们来看看这些规则的一些后果。例如,考虑以下用于计算列表列表交集的代码:
#lang scheme
(define intersectall
(lambda (lset)
(let/cc hop
(letrec
[[A (lambda (lset)
(cond [(null? (car lset)) (hop '())]
[(null? (cdr lset)) (car lset)]
[else (I (car lset) (A (cdr lset)))]))]
[I (lambda (s1 s2)
(letrec
[[J (lambda (s1)
(cond [(null? s1) '()]
[(M? (car s1) s2) (cons (car s1) (J (cdr s1)))]
[else (J (cdr s1))]))]
[M? (lambda (el s)
(letrec
[[N? (lambda (s)
(cond [(null? s) #f]
[else (or (eq? (car s) el) (N? (cdr s)))]))]]
(N? s)))]]
(cond [(null? s2) (hop '())]
[else (J s1)])))]]
(cond [(null? lset) '()]
[else (A lset)])))))
这个例子出现在第 13 章(不完全像这样:我粘贴了在前一段中单独定义的成员资格测试代码)。
我认为以下替代实现,它的使用非常有限,letrec
并且letcc
更具可读性和更易于理解:
(define intersectall-naive
(lambda (lset)
(letrec
[[IA (lambda (lset)
(cond [(null? (car lset)) '()]
[(null? (cdr lset)) (car lset)]
[else (intersect (car lset) (IA (cdr lset)))]))]
[intersect (lambda (s1 s2)
(cond [(null? s1) '()]
[(M? (car s1) s2) (cons (car s1) (intersect (cdr s1) s2))]
[else (intersect (cdr s1) s2)]))]
[M? (lambda (el s)
(cond [(null? s) #f]
[else (or (eq? (car s) el) (M? el (cdr s)))]))]]
(cond [(null? lset) '()]
[else (IA lset)]))))
我是方案新手,我的背景不是计算机科学,但令我震惊的是,对于一个简单的列表交集问题,我们必须得到如此复杂的代码。这让我想知道人们如何管理现实世界应用程序的复杂性。经验丰富的策划者是否每天都在深度嵌套letcc
和letrec
表达?
这是询问 stackexchange 的动机。
我的问题是:Friedman 和 Felleisen 是否为了教育而过度复杂化了这个示例,还是出于性能原因我应该习惯于使用充满letcc
s 和s 的方案代码?letrec
对于大型列表,我的幼稚代码是否会变得不切实际?