【发布时间】:2021-01-28 02:27:15
【问题描述】:
我正在阅读 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)]))))
我是 scheme 新手,我的背景不是计算机科学,但让我感到震惊的是,对于一个简单的列表交集问题,我们必须以如此复杂的代码结束。这让我想知道人们如何管理现实世界应用程序的复杂性。
经验丰富的策划者是否每天都在深度嵌套 letcc 和 letrec 表达式?
这就是询问 stackexchange 的动机。
我的问题是:Friedman 和 Felleisen 是否为了教育而过度复杂化了这个示例,还是出于性能原因我应该习惯于使用充满 letccs 和 letrecs 的方案代码?
对于大型列表,我的幼稚代码是否会变得非常慢?
【问题讨论】:
-
他们至少可以使用一些
let绑定来避免所有那些使代码完全不可读的重复的car和cdr调用。好的代码应该在视觉上很明显。表面语法很糟糕,迫使你思考平凡的东西,而不是简单地看到它。这是混淆,就是这样。语法好(即使是just some made-up pseudocode)很容易理解,甚至很容易变成不需要任何继续捕获,这实际上是性能杀手。 -
嗨@WillNess,你的伪代码看起来很不错,哪种语言给了你灵感?查看您的解决方案让我认为,在这种情况下,现实世界的代码可能会广泛使用模式匹配。不幸的是,The Seasoned Schemer 没有引入模式匹配(至少现在还没有,我已经完成了一半)。恐怕使用
let绑定来避免car和cdr调用不会节省很多空间,因为let在绑定之前进行评估,并且我必须确保列表实际上有cdr在询问之前为其cdr并将其绑定到一个变量。 -
部分是 Haskell,部分是我的 evolving imagination。我什至一开始就想到了
[a, ...d...]语法,后来发现JS 已经有了[a, ...d](很长时间了?)。 (但我不知道 JS。有一个我不知道的语言的日志列表,python、clojure、ruby 等)。是的,过早的cdr是一个问题,但是对于伪代码,我们希望它消失,说“这是一个定义,而不是立即行动的指令”。 -
(contd.) 我认为
{x => x}的 lambda 语法也在 JS 中。 (在 Haskell 中,let/where绑定是惰性的,直到变量真正被访问后才会执行)。代码分析器可以将伪代码转换为“真正的方案”......等等。---顺便说一句,我认为,如果等式一一尝试,我的伪代码中的早熟car/cdr没有问题自上而下的顺序)。我们有一个单独的第一个方程intersectall [] = [],因此第二个方程中的lset保证为非空。假设我们只执行 one,第一个匹配的方程。与 Prolog 不同,Prolog 全部完成。