我可以将Common Lisp用于SICP,还是Scheme是唯一的选择?

时间:2009-07-21 13:34:07

标签: lisp scheme common-lisp sicp

另外,即使我可以使用Common Lisp,我应该吗?方案更好吗?

6 个答案:

答案 0 :(得分:109)

答案 1 :(得分:14)

使用带有Common Lisp的SICP是可行和有趣的

您可以使用Common Lisp与SICP一起学习,而不会出现太多问题。本书中使用的Scheme子集不是很复杂。 SICP不使用宏,它不使用延续。有DELAY和FORCE,可以用Common Lisp编写几行。

对于使用(function foo)(funcall foo 1 2 3)的初学者来说实际上更好(恕我直言!),因为在学习函数式编程部分时代码会更清晰。您可以看到调用/传递变量和lambda函数的位置。

Common Lisp中的尾调用优化

使用Common Lisp只有一个很大的缺点是:尾部调用优化(TCO)。 Common Lisp在其标准中不支持TCO(因为与其他语言的交互不清楚,并非所有计算机体系结构都直接支持它(想想JVM),并非所有编译器都支持它(一些Lisp机器),它会进行一些调试/跟踪/踩得更厉害,......)。

有三种方法可以解决这个问题:

  1. 希望筹码不会爆炸。 BAD。
  2. 使用支持TCO的Common Lisp实现。有一些。见下文。
  3. 使用DOTIMES,DO,LOOP,......将函数循环(和类似的构造)重写为循环(和类似的构造)...
  4. 我个人推荐2或3。

    Common Lisp具有优秀且易于使用的具有TCO支持的编译器(SBCL,LispWorks,Allegro CL,Clozure CL,...),并且作为开发环境使用内置的或GNU Emacs / SLIME。

    为了与SICP一起使用,我建议使用SBCL,因为它总是默认编译,默认情况下有TCO支持,并且编译器捕获了很多编码问题(未声明的变量,错误的参数列表,一堆类型错误,...)。这在学习过程中有很大帮助。通常要确保编译代码,因为Common Lisp解释器通常不支持TCO。

    有时,编写一个或两个宏并提供一些Scheme函数名称以使代码看起来更像Scheme,也可能会有所帮助。例如,您可以在Common Lisp中使用DEFINE宏。

    对于更高级的用户,有一个用Common Lisp(称为Pseudo Scheme)编写的旧的Scheme实现,它应该运行SICP中的大部分代码。

    我的建议:如果你想加倍努力并使用Common Lisp,那就去做吧。

    为了更容易理解必要的更改,我添加了一些示例 - 请记住,它需要一个支持尾部调用优化的Common Lisp编译器

    示例

    让我们看看SICP的这个简单代码:

    (define (factorial n)
      (fact-iter 1 1 n))
    
    (define (fact-iter product counter max-count)
      (if (> counter max-count)
          product
          (fact-iter (* counter product)
                     (+ counter 1)
                     max-count)))
    

    我们可以在Common Lisp中使用DEFINE宏直接使用它:

    (defmacro define ((name &rest args) &body body)
      `(defun ,name ,args ,@body))
    

    现在你应该使用SBCL,CCL,Allegro CL或LispWorks。这些编译器默认支持TCO。

    让我们使用SBCL:

    * (define (factorial n)
        (fact-iter 1 1 n))
    ; in: DEFINE (FACTORIAL N)
    ;     (FACT-ITER 1 1 N)
    ; 
    ; caught STYLE-WARNING:
    ;   undefined function: FACT-ITER
    ; 
    ; compilation unit finished
    ;   Undefined function:
    ;     FACT-ITER
    ;   caught 1 STYLE-WARNING condition
    
    FACTORIAL
    * (define (fact-iter product counter max-count)
        (if (> counter max-count)
            product
            (fact-iter (* counter product)
                       (+ counter 1)
                       max-count)))
    
    FACT-ITER
    * (factorial 1000)
    
    40238726007709....
    

    另一个例子:象征性差异化

    SICP有一个区分的方案示例:

    (define (deriv exp var)
      (cond ((number? exp) 0)
            ((variable? exp)
             (if (same-variable? exp var) 1 0))
            ((sum? exp)
             (make-sum (deriv (addend exp) var)
                       (deriv (augend exp) var)))
            ((product? exp)
             (make-sum
               (make-product (multiplier exp)
                             (deriv (multiplicand exp) var))
               (make-product (deriv (multiplier exp) var)
                             (multiplicand exp))))
            (else
             (error "unknown expression type -- DERIV" exp))))
    

    使用Common Lisp运行此代码很简单:

    • 某些功能有不同的名称,number?在{<1}}中是numberp
    • CL:COND使用T代替else
    • CL:ERROR使用CL格式字符串

    让我们为一些函数定义Scheme名称。 Common Lisp代码:

    (loop for (scheme-symbol fn) in
          '((number?      numberp)
            (symbol?      symbolp)
            (pair?        consp)
            (eq?          eq)
            (display-line print))
          do (setf (symbol-function scheme-symbol)
                   (symbol-function fn)))
    

    我们上面的define宏:

    (defmacro define ((name &rest args) &body body)
      `(defun ,name ,args ,@body))
    

    Common Lisp代码:

    (define (variable? x) (symbol? x))
    
    (define (same-variable? v1 v2)
      (and (variable? v1) (variable? v2) (eq? v1 v2)))
    
    (define (make-sum a1 a2) (list '+ a1 a2))
    
    (define (make-product m1 m2) (list '* m1 m2))
    
    (define (sum? x)
      (and (pair? x) (eq? (car x) '+)))
    
    (define (addend s) (cadr s))
    
    (define (augend s) (caddr s))
    
    (define (product? x)
      (and (pair? x) (eq? (car x) '*)))
    
    (define (multiplier p) (cadr p))
    
    (define (multiplicand p) (caddr p))
    
    (define (deriv exp var)
      (cond ((number? exp) 0)
            ((variable? exp)
             (if (same-variable? exp var) 1 0))
            ((sum? exp)
             (make-sum (deriv (addend exp) var)
                       (deriv (augend exp) var)))
            ((product? exp)
             (make-sum
               (make-product (multiplier exp)
                             (deriv (multiplicand exp) var))
               (make-product (deriv (multiplier exp) var)
                             (multiplicand exp))))
            (t
             (error "unknown expression type -- DERIV: ~a" exp))))
    

    让我们在LispWorks中尝试一下:

    CL-USER 19 > (deriv '(* (* x y) (+ x 3)) 'x)
    (+ (* (* X Y) (+ 1 0)) (* (+ (* X 0) (* 1 Y)) (+ X 3)))
    

    Common Lisp中来自SICP的Streams示例

    请参阅SICP中的book code in chapter 3.5。我们使用上面的CL添加。

    SICP提及delaythe-empty-streamcons-stream,但未实施。我们在这里提供了Common Lisp中的一个实现:

    (defmacro delay (expression)
      `(lambda () ,expression))
    
    (defmacro cons-stream (a b)
      `(cons ,a (delay ,b)))
    
    (define (force delayed-object)
      (funcall delayed-object))
    
    (defparameter the-empty-stream (make-symbol "THE-EMPTY-STREAM"))
    

    现在出现了书中的可移植代码:

    (define (stream-null? stream)
      (eq? stream the-empty-stream))
    
    (define (stream-car stream) (car stream))
    
    (define (stream-cdr stream) (force (cdr stream)))
    
    (define (stream-enumerate-interval low high)
      (if (> low high)
          the-empty-stream
        (cons-stream
         low
         (stream-enumerate-interval (+ low 1) high))))
    

    现在Common Lisp在stream-for-each中有所不同:

    • 我们需要使用cl:progn代替begin
    • 需要使用cl:funcall
    • 调用函数参数

    这是一个版本:

    (defmacro begin (&body body) `(progn ,@body))
    
    (define (stream-for-each proc s)
      (if (stream-null? s)
          'done
          (begin (funcall proc (stream-car s))
                 (stream-for-each proc (stream-cdr s)))))
    

    我们还需要使用cl:function传递函数:

    (define (display-stream s)
      (stream-for-each (function display-line) s))
    

    但是这个例子有效:

    CL-USER 20 > (stream-enumerate-interval 10 20)
    (10 . #<Closure 1 subfunction of STREAM-ENUMERATE-INTERVAL 40600010FC>)
    
    CL-USER 21 > (display-stream (stream-enumerate-interval 10 1000))
    
    10 
    11 
    12 
    ...
    997 
    998 
    999 
    1000 
    DONE
    

答案 2 :(得分:11)

你是否已经了解一些Common Lisp?我认为这就是'Lisp'的意思。在这种情况下,您可能希望使用它而不是Scheme。如果您不知道,并且您正在通过SICP完成学习体验,那么您可能会更好地使用Scheme。它对新学习者有更好的支持,你不必从Scheme翻译成Common Lisp。

存在差异;具体来说,SICP的高功能风格在Common Lisp中比较简单,因为你必须在传递函数时引用函数并使用funcall来调用绑定到变量的函数。

但是,如果您想使用Common Lisp,可以尝试使用Eli Bendersky's Common Lisp translations of the SICP code标记下的SICP

答案 3 :(得分:2)

编辑: Nathan Sanders的评论是正确的。自从我上次阅读本书以来,显然已经有一段时间了,但我刚刚检查过它并没有直接使用call/cc。我赞成了内森的回答。


无论你使用什么都需要实现延续,SICP使用了很多。甚至所有的Scheme解释器都没有实现它们,我也不知道有任何Common Lisp。

答案 4 :(得分:1)

它们相似但不一样。

我相信如果你选择Scheme会更容易。

答案 5 :(得分:-1)

我会选择像Clojure这样更实用的方言,Clojure在JVM上运行并且可以使用所有Java库,这是一个巨大的优势。 Clojure也是现代的Lisp,拥有很好的概念。它有不断增长的社区。如果你想尝试Clojure,我建议你Clojurecademy这是我为新人创建的基于Clojure的互动课程平台。