哪个更惯用的方案?

Which is more Idiomatic scheme?

我正在研究 SICP,对于符号微分器,我想出了两种方法来对任意数量的参数求和。

许多参数的一个 returns 总和为:

(make-sum 1 'x 4) -> (+ 4 'x)

(make-sum '(+ 3 x) (** x 2) 5) -> (+ 8 x (** x 2))

另一个是:

(make-sum 1 'x 4) ->(+ 1 (+ 'x 4))

(make-sum '(+ 3 x) (** x 2) 5) -> (+ (+3 x) (+ (** x 2) 5))

请注意,make-sum 均未完全发挥作用。我的问题不是弄清楚如何解决这些问题。

第一个make-sum

(define (make-sum . ad)
  #|goes through the list ad
  and adds up the numbers, while
  putting non-numeric terms into 
  a separate list, returning a new 
  list with the sum and non-numeric
  term list|#
  (define (accumulate-terms lst)
    (define (accumulate-terms-help lst num-sum other-terms)
      (cond ((null? lst) `(,@other-terms ,num-sum))
            ((number? (car lst)) 
             (accumulate-terms-help (cdr lst)
                                (+ num-sum (car lst))
                                other-terms))
            (else (accumulate-terms-help (cdr lst)
                                     num-sum
                         (cons (car lst) other-terms)))))
    (accumulate-terms-help lst 0 '()))

  #|modified flatten that only flattens
  sub-lists that have sym as their first element|#
  (define (flatten lst sym)
    (cond ((eq? lst '()) '())
          ((list? (car lst))
          (cond ((eq? (caar lst) sym)
             (append (flatten (car lst) sym) (flatten (cdr lst) sym)))
            (else (cons (car lst) (flatten (cdr lst) sym)))))     
          (else
           (cons (car lst) (flatten (cdr lst) sym)))))

  #|flattens out all addition sub terms
  accumulates what is left and then filters
  any zeroes|#
  (let* ()
    (define ret
      (filter (lambda (p)
            (not (eq? p 0)))
              (cond ((> (length ad) 1)
                 `(+ ,@(accumulate-terms
                     (filter (lambda (q)
                           (not (eq? q '+)))
                         (flatten ad '+)))))
                    (else ad))))

    (cond ((> (length ret) 2)
           ret)
          (else (cadr ret)))))

第二次求和

(define (make-sum . an)
  (cond
    ((equal? (length an) 1)
      (let ((it (car an)))
        (cond
          ((number? it) it)
          ((variable? it) `',it)
          ((sum? it) (eval `(make-sum ,@it)))
          (else it))))
    (else
      (let ((cur (car an))
           (rest (cdr an)))
        (cond
          ((number? cur)
             `(+ ,cur ,(eval `(make-sum ,@rest))))
          ((variable? cur)
             `(+ ',cur ,(eval `(make-sum ,@rest))))     
          ((sum? cur)
             (let ((ad (addend cur))
                   (ag (augend cur)))
               (cond
               #|if both the addend and augend of cur
               are numbers, returns the sum|#
                 ((and (number? ad)
                   (number? ag))
                   `(+ ,(+ ad ag)
                   ,(eval `(make-sum ,@rest))))

               #|if the addend of cur is a number
               and the augend of cur is a sum|#
                 ((and (number? ad)
                   (sum? ag))
                   (let ((adg (addend ag))
                         (agg (augend ag)))
                     (cond
                       ((number? adg)
                    `(+ ,(+ ad adg)
                        ,(eval `(make-sum agg ,@rest))))

                       ((number? agg)
                    `(+ ,(+ ad agg)
                        ,(eval `(make-sum adg ,@rest))))

                       (else `(+ ,ad
                         ,(eval `(make-sum ,ag
                                   ,@rest)))))))

                 (else `(+ ,cur (eval `(make-sum ,@rest))))))))))))

所以我的问题是,哪种方法是 "schemier" 做事的方式, 展平和过滤列表以将其转换为正确的列表, 还是通过每个递归级别的规则手动递归? 第一个示例的优势在于其输出 (make-sum 'a 'b 'c) -> (+ a b c) 比第二个 (make-sum 'a 'b 'c) -> (+ a (+ b c)) 更具可读性。第二个优点是你可以更容易地求导数,只使用两个函数,addendaugend 到 select 运算符,导数以你处理的方式自然地表达与他们一起进行微积分 class。第一个示例更难求导,需要将 deriv 映射到每个项和一些过滤器以确保正确输出。

编辑 此外,我不喜欢我必须如此频繁地使用 eval,但这是我能想到的将解压缩列表输入到

之类的函数的唯一方法
(eval `(foo ,@lst)) ;foo takes any number of arguments

此外:

`',var ; for quoting the result from var
       ; when this term is inside of a qq

似乎有更好的方法去做

在 Scheme 中,惯用的方法是使用 higher-order 函数 - 根据 mapfilterfoldlfoldr 编写您的解决方案, flatten, 等等(还有更多!)这将导致更短的解决方案,而不是必须显式循环遍历每个列表。

此外,避免滥用quasi-quoting、拼接和eval :-)。您的所有代码都可以在不使用它们的情况下重写,在本书的这一点上,作者还没有涵盖它们。例如,这个:

(eval `(foo ,@lst))

相当于:

(apply foo lst)

以上是首选方式。同样,这没有多大意义:

`',var

如果var已经是一个符号,那么就保留它,你不必再次引用它。底线:保持简单,您的代码无缘无故地过于复杂。