另外,即使我可以使用Common Lisp,我应该吗?方案更好吗?
答案 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机器),它会进行一些调试/跟踪/踩得更厉害,......)。
有三种方法可以解决这个问题:
我个人推荐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提及delay
,the-empty-stream
和cons-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的互动课程平台。