Little Schemer evens-only *& co

时间:2012-05-21 20:51:21

标签: recursion lambda scheme continuation-passing the-little-schemer

我很难理解第14页的The Little Schemer evens-only*&co示例所发生的事情。

以下是代码:

(define evens-only*&co
 (lambda (l col)
   (cond
    ((null? l)
     (col '() 1 0))
    ((atom? (car l))
     (cond
      ((even? (car l))
       (evens-only*&co (cdr l)
                    (lambda (newl product sum)
                      (col (cons (car l) newl)
                           (opx (car l) product)
                           sum))))
      (else
       (evens-only*&co (cdr l)
                    (lambda (newl product sum)
                      (col newl product (op+ (car l) sum)))))))
    (else
     (evens-only*&co (car l)
                  (lambda (newl product sum)
                    (evens-only*&co (cdr l)
                                    (lambda (dnewl dproduct dsum)
                                      (col (cons newl dnewl)
                                           (opx product dproduct)
                                           (op+ sum dsum))))))))))

最初的col可以是:

(define evens-results
 (lambda (newl product sum)
   (cons sum (cons product newl))))

我没有得到的是,l'((1) 2 3),它会立即进入最终else(car l)(1)和{(cdr l) 1}}为(2 3)。很好,但是我的想法是空白,试图从dnewldproductdsum中挑选newlproductsum。如果有人可以指导我如何设置DrRacket或Chez Scheme或MIT-Scheme来运行步进器,那将会很有帮助。

但也许我太早了。是否有任何初学者第一次读到这个实际上应该理解这种狂野的延续?

4 个答案:

答案 0 :(得分:20)

我发现这一节在第一次阅读时也很混乱,只是在我读完其他关于延续和延续传递风格的事后才开始得到它(这就是这个)。

冒着解释你已经得到的东西的风险,一种看待它的方法有助于我将“收集器”或“延续”视为取代函数返回值的正常方式。在正常的编程风格中,您可以调用函数,接收值,并在调用者中对其执行某些操作。例如,标准递归length函数包含非空案例的表达式(+ 1 (length (cdr list)))。这意味着一旦(length (cdr list))返回一个值,就会有一个计算等待它产生的任何值,我们可以将其视为(+ 1 [returned value])。在正常编程中,解释器会跟踪这些待处理的计算,这些计算往往会“叠加”,正如您在本书的前几章中所看到的那样。例如,在递归地计算列表的长度时,我们有一个“等待计算”的嵌套,因为列表很长,所以有很多级别。

在延续传递样式中,我们告诉函数在生成其值时通过为其提供“延续”而不是调用函数并使用返回的结果“打电话。 (这类似于您在异步Javascript编程中使用回调所做的事情,例如:您编写result = someFunction();而不是编写someFunction(function (result) { ... }),而使用result的所有代码都在内部回调函数)。

此处为继续传递样式的length,仅供比较。我已经调用了continuation参数return,它应该在这里建议它如何起作用,但请记住它只是一个普通的Scheme变量,就像其他任何变量一样。 (通常,continuation参数在此样式中称为k。)

(define (length/k lis return)
  (cond ((null? lis) (return 0))
        (else
         (length/k (cdr lis)
                   (lambda (cdr-len)
                     (return (+ cdr-len 1)))))))

an article on continuations by Little Schemer co-author Dan Friedman中阅读此类代码有一个有用的提示。 (参见第8页开始的第II-5节)。解释,这是上面的else条款所说的:

  

假设您在length/k上调用(cdr lis)的结果,并且   称之为cdr-len,然后添加一个并传递此添加的结果   继续(return)。

请注意,这几乎就是解释器在评估函数正常版本中的(+ 1 (length (cdr lis)))时必须做的事情(除了它不必为中间结果(length (cdr lis))命名通过传递延续或回调,我们使控制流(以及中间值的名称)显式化,而不是让解释器跟踪它。

让我们将此方法应用于evens-only*&co中的每个子句。这里有点复杂,因为这个函数产生三个值而不是一个:删除奇数的嵌套列表;偶数的乘积;和奇数的总和。这是第一个子句,其中(car l)已知为偶数:

(evens-only*&co (cdr l)
                (lambda (newl product sum)
                  (col (cons (car l) newl)
                       (opx (car l) product)
                       sum)))
  

想象一下你有删除奇数的结果,   乘以平均值,并从列表的cdr中添加奇数,   并分别称他们为newlproductsumcons   列表的头部newl(因为它是偶数,它应该去   在结果中);将product乘以列表的头部(因为   我们正在计算平均值的产品);只留下sum;并通过这些   等待继续col的三个值。

以下是列表头部为奇数的情况:

(evens-only*&co (cdr l)
                (lambda (newl product sum)
                  (col newl product (op+ (car l) sum))))

和以前一样,但是将newlproduct的相同值传递给延续(即“返回”它们),以及sum和列表头部的总和因为我们总结了奇数。

这是最后一个,其中(car l)是一个嵌套列表,并且由于双递归而略显复杂:

(evens-only*&co (car l)
                (lambda (newl product sum)
                  (evens-only*&co (cdr l)
                                  (lambda (dnewl dproduct dsum)
                                    (col (cons newl dnewl)
                                         (opx product dproduct)
                                         (op+ sum dsum))))))
  

想象一下,你有删除,求和和添加的结果   (car l)中的数字并调用这些newlproductsum;然后   想象一下你对(cdr l)做同样的事情有结果,   并称他们为dnewldproductdsum。等你   继续,给出cons newldnewl生成的值   (因为我们正在制作一份清单清单);相乘   productdproduct;并添加sumdsum

注意:每次进行递归调用时,我们都会为递归调用构造一个新的延续,它会“关闭”参数的当前值l和返回延续 - {{1}换句话说,你可以想象我们在递归过程中建立的连续链,就像建模一个更常规编写的函数的“调用堆栈”一样!

希望能为你的问题提供部分答案。如果我有点过分了,那只是因为我认为,在递归本身之后,延续是 The Little Schemer 中第二个非常整洁,思想扩展的想法和一般的编程。

答案 1 :(得分:1)

answer Jon O.是对基本概念的非常深入的解释。虽然对我来说(并且希望对其他人也是如此),但是当他们有视觉表现时,理解这样的概念要容易得多。

所以,我准备了两个流程图(类似于ones I did for multirember&co,解决了在调用evens-only*&co

期间发生的事情

给定l是:

'((9 1 2 8) 3 10 ((9 9) 7 6) 2) 

col是:

(define the-last-friend
    (lambda (newl product sum)
        (cons sum (cons product newl))
    )
)

一个流程图,反映变量在递归的不同步骤中的关系Visual walkthrough showing relations between variables 第二个流程图,显示实际值,正在传递Visual walkthrough with values

我的希望是,这个答案将成为Jon's explanation above的一个不错的补充。

答案 2 :(得分:0)

我一直在阅读如何设计程序(felleisen et.al.)。我正在浏览他们定义本地定义的部分。我编写了一个使用本地定义实现上述evens-only& co的代码。这是我写的:

(define (evens-only&co l)
  (local ((define (processing-func sum prod evlst lst)
            (cond ((null? lst) (cons sum (cons prod evlst)))
                  ((atom? (car lst))
                   (cond ((even? (car lst)) (processing-func sum (* prod (car lst)) (append evlst (list (car lst))) (cdr lst)))
                         (else
                          (processing-func (+ sum (car lst)) prod evlst (cdr lst)))))
                  (else
                   (local ((define inner-lst (processing-func sum prod  '() (car lst))))
                   (processing-func (car inner-lst) (cadr inner-lst) (append evlst (list (cddr inner-lst))) (cdr lst)))))))
    (processing-func 0 1 '() l)))

为了进行测试,当我进入(evens-only& co'((9 1 2 8)3 10((9 9)7 6)2))时,它返回'(38 1920(2 8)10(( )6)2)正如小阴谋家所料。但是,我的代码在一个条件下失败:当根本没有偶数时,evens的乘积仍显示为1.例如(evens-only& co'((9 1)3((9 9)7) ))返回'(38 1()(()))。我想我需要一个额外的功能来纠正这个问题。 @melwasul:如果您不熟悉本地定义,很抱歉在此发布。我建议你也阅读HTDP。这对初学者来说是一本很好的书。 但是作为计划专家的人也可以在我的代码上发表评论。我对本地定义的理解是否正确?

答案 3 :(得分:0)

在等式伪代码(一个KRC - 表示法中,为f x y编写(f x y),这是明确的,这是

evens-only*&co l col 
   = col [] 1 0                                     , IF null? l
   = evens-only*&co (cdr l) 
                    ( newl product sum =>
                        col (cons (car l) newl)
                            (opx (car l) product)
                            sum )                   , IF atom? (car l) && even? (car l)
   = evens-only*&co (cdr l) 
                    ( newl product sum =>
                        col  newl  product  (op+ (car l) sum) )      , IF atom? (car l)
   = evens-only*&co (car l) 
                    ( anewl aproduct asum =>
                         evens-only*&co (cdr l)
                                        ( dnewl dproduct dsum =>
                                             col (cons anewl    dnewl)
                                                 (opx  aproduct dproduct)
                                                 (op+  asum     dsum) ) )   , OTHERWISE

这是一个CPS代码,它在保留树结构的同时从输入嵌套列表(即树)中收集所有均值,并且还找到所有均值的乘积;至于非均衡,它总结

  • 如果l是一个空列表,则三个基本(标识)值作为参数传递给col;

  • 如果(car l)是偶数,则处理(cdr l)的结果为newlproductsum和然后将它们作为参数传递给col,而前两个则通过使用(car l)(偶数)来增加/增加;

  • 如果(car l)是非偶数的原子,则处理(cdr l)的结果为newlproductsum然后它们作为参数传递给col,第三个通过与(car l)(非偶数原子)求和来增加;

  • 如果(car l)是列表,则处理(car l)的结果为anewlaproductasum,然后处理(cdr l)的结果为dnewldproductdsum然后三个组合结果作为参数传递到col

基本情况的

[]10分别是monoids列表的标识元素,乘法下的数字和加法下的数字。这只是意味着在结合到结果中时不会改变结果的特殊值。

作为一个例子,对于'((5) 2 3 4)(与问题中的示例接近),它会创建计算

evens-only*&co [[5], 2, 3, 4] col
=
col  (cons []                   ; original structure w only the evens kept in,
           (cons 2              ;   for the car and the cdr parts
              (cons 4 [])))
     (opx 1                     ; multiply the products of evens in the car and 
          (opx 2 (opx 4 1)))    ;   in the cdr parts
     (op+ (op+ 5 0)             ; sum, for the non-evens
          (op+ 3 0))     

类似于my other answer(对于姐妹问题),这是另一种写这个的方式,带有模式匹配的伪代码(带有警卫):

evens-only*&co  =  g   where
  g [a, ...xs...] col 
         | pair? a    = g a  ( la pa sa =>
                         g xs ( ld pd sd =>
                                        col [la, ...ld...] (* pa pd) (+ sa sd) ) )
         | even? a    = g xs ( l p s => col [ a, ...l... ] (* a  p )       s     )
         | otherwise  = g xs ( l p s => col         l            p   (+ a  s )   )
  g []            col =                 col []              1         0

这种符号的经济性(和多样性)确实使得它更加清晰,更容易只看到而不是迷失在长期名称沙拉中的功能和变量等,而且parens重载作为列表数据的句法分隔符,子句分组(如cond表达式中),名称绑定(在lambda表达式中)和函数调用指示符所有正在完全相似< / em>的。 S表达式符号的相同均匀性有利于机器操作的简便性(即lisp的 read 和宏)是对人类可读性的不利影响它的。