我最近开始和朋友一起阅读Paul Graham的On Lisp,我们意识到我们对reduce有不同的看法:我认为它非常清晰简洁地表达某种递归形式;他更喜欢非常清楚地写出递归。
我怀疑我们在某些情况下是正确的而在另一种情况下是错误的,但我们不知道这条线在哪里。你什么时候选择一种形式而不是另一种形式?在做出这种选择时你会怎么想?
要清楚我对reduce和显式递归的意思,这里实现了两次相同的函数:
(defun my-remove-if (pred lst)
(fold (lambda (left right)
(if (funcall pred left)
right
(cons left right)))
lst :from-end t))
(defun my-remove-if (pred lst)
(if lst
(if (funcall pred (car lst))
(my-remove-if pred (cdr lst))
(cons (car lst) (my-remove-if pred (cdr lst))))
'()))
我害怕我开始使用Schemer(现在我们是Racketeers?)所以如果我搞砸了Common Lisp语法,请告诉我。希望即使代码不正确,这一点也很清楚。
答案 0 :(得分:11)
如果您有选择,您应该始终以最抽象的术语表达您的计算意图。这使读者更容易理解您的意图,并使编译器更容易优化您的代码。在您的示例中,当编译器通过命名它而非常简单地知道您正在执行折叠操作时,它也很容易知道它可能并行化叶操作。当你编写极低级别的操作时,编译器很难弄明白。
答案 1 :(得分:4)
在Common Lisp中,人们更倾向于使用高阶函数来进行数据结构遍历,过滤和其他相关的递归操作。这也可以从许多提供的函数中看到,例如REDUCE,REMOVE-IF,MAP等。
尾递归是a)标准不支持,b)可能使用不同的CL编译器进行不同的调用,c)使用尾递归可能会对周围代码生成的机器代码产生副作用。
通常,对于某些数据结构,上述许多操作都是使用LOOP或ITERATE实现的,并作为高阶函数提供。对于迭代代码而言,倾向于使用新的语言扩展(如LOOP和ITERATE),而不是使用递归进行迭代。
(defun my-remove-if (pred list)
(loop for item in list
unless (funcall pred item)
collect item))
这是一个使用Common Lisp函数REDUCE的版本:
(defun my-remove-if (pred list)
(reduce (lambda (left right)
(if (funcall pred left)
right
(cons left right)))
list
:from-end t
:initial-value nil))
答案 2 :(得分:3)
我将采取一个略微主观的问题,给出一个高度主观的答案,因为艾拉已经给出了一个完全务实和合乎逻辑的答案。 : - )
我知道明确地写出来的东西在某些圈子中受到高度重视(Python人员将其作为“zen”的一部分),但即使在我编写Python时我也从不理解它。我想一直写在最高级别。当我想明确地写出来时,我使用汇编语言。使用计算机(和HLL)的目的是让它为我做这些事情!
对于你的my-remove-if
示例,reduce对我来说很好看(除了像fold
和lst
这样的方案主义:-))。我熟悉reduce的概念,所以我需要理解的是找出你的f(x,y) -> z
。对于显式变体,我不得不考虑一下:我必须自己弄清楚循环。递归并不是最困难的概念,但我认为它比“两个参数的函数”更难。
我也不在乎重复整行 - (my-remove-if pred (cdr lst))
。我觉得我喜欢Lisp部分原因是因为我在DRY上绝对是无情而且Lisp允许我在其他语言没有的轴上干。 (你可以在顶部加上另一个LET
以避免这种情况,但是它会更长更复杂,我认为这是另一个更喜欢减少的原因,尽管此时我可能只是合理化。)
我想也许Python家伙至少不喜欢隐含功能的背景可能是:
frobnicate("hello, world", True)
- 真是什么意思?),或者:True
参数被移动,删除或替换为其他内容,因为在大多数动态语言中没有编译时错误)但Lisp中的reduce
未能达到这两个标准:这是一个众所周知的抽象概念,并且不会改变,至少在我关心的任何时间尺度都没有。
现在,我绝对相信 某些情况下我会更容易阅读明确的函数调用,但我认为你必须非常有创意才能想出它们。我无法想到任何副手,因为reduce
和mapcar
以及朋友非常好抽象。