在 windows 上重新安装了最新的 Racket 5.2.1.
恍然发现,Common-Lisp 的安装真的比较坑爹啊,Racket 可能才是研究和学习 lisp 比较理想的选择!
不管是 Windows 还是 Ubuntu,自学习 Lisp 以来,Common Lisp 的各种实现 + 开发环境也安装了很多了,每一个配置起来都比较麻烦,也都有这样那样的问题。而相比而言,安装 Racket 却超级傻瓜化。
一番周折后,终于调通了 SICP 220 页开始的对于 stream 的一些示例代码。其中需要注意的是:
书中提到的 cons-stream 是一个 special form, 但是我一开始写成了简单的 function 如下:
; this won't work as a simple function
(define (cons-stream a b)
(cons a (delay b)))
但这个实际是是不 work 的。如果流比较大或者是无限流,几乎一定会导致无限递归、内存溢出。解决的办法参考了 stack overflow 上一个答案,利用 Racket 的 define-syntax 语法定义宏。其实和 Common Lisp 里宏有点接近,只是定义时使用的语法有差异,用到方括号,详见代码。
和 Common Lisp 中列表的 subseq 函数相似,我简单的实现了一个 stream-subseq 函数,用于截取流中某一区间的子序列,配合 display-stream 函数打印会比较方便的了解流中间任意一段的信息,后面的测试输出代码我基本都这么写了。
通过无限流移位后相加、相乘甚至配合其他运算进行任意组合,是流使用起来感觉最妙的地方。代码中包含了 SICP 原书中附带的一个筛法求素数序列的实现。
通过几个例子简单的试验下来发现,的确 stream 的功效是强大的。因为本质上每一步计算都是惰性求值,所以即使是要估算序列中很后面的数值,也不会像列表那样带来很多分配空间的开销。斐波那契数列可以轻而易举的算到很后面。下面是我调试通过的测试代码:
#lang racket
;(define (delay exp)
; (lambda () exp))
; ;(memo-proc (lambda ()
; ; exp)))
;
;(define (force delayed-object)
; (delayed-object))
;
;(define (memo-proc proc)
; (let ((already-run? false) (result false))
; (lambda ()
; (if (not already-run?)
; (begin (set! result (proc))
; (set! already-run? true)
; result)
; result))))
(define (stream-car stream) (car stream))
(define (stream-cdr stream) (force (cdr stream)))
; this won't work as a simple function
;(define (cons-stream a b)
; (cons a (delay b)))
; This is scheme syntax for macro
; http://stackoverflow.com/questions/5610480/scheme-sicp-r5rs-why-is-delay-not-a-special-form
(define-syntax cons-stream
(syntax-rules ()
[(cons-stream x y) (cons x (delay y))]))
(define the-empty-stream '())
(define (stream-null? stream)
(null? stream))
(define (stream-filter pred stream)
(cond ((stream-null? stream) the-empty-stream)
((pred (stream-car stream))
(cons-stream (stream-car stream)
(stream-filter pred (stream-cdr stream))))
(else (stream-filter pred (stream-cdr stream)))))
(define (stream-ref s n)
(if (stream-null? s) the-empty-stream
(if (= n 0)
(stream-car s)
(stream-ref (stream-cdr s) (- n 1)))))
(define (stream-map proc . argstreams)
(if (stream-null? (car argstreams))
the-empty-stream
(cons-stream (apply proc (map stream-car argstreams))
(apply stream-map
(cons proc (map stream-cdr argstreams))))))
(define (stream-for-each proc s)
(if (stream-null? s)
'done
(begin (proc (stream-car s))
(stream-for-each proc (stream-cdr s)))))
; Neil, 2012-05-10
(define (stream-subseq stream a b)
(cond ((stream-null? stream) the-empty-stream)
((= a b) the-empty-stream)
((> a b) the-empty-stream)
(else (cons-stream (stream-ref stream a)
(stream-subseq stream (+ a 1) b)))))
(define (display-line x)
(newline)
(display x))
(define (display-stream s)
(stream-for-each display-line s))
; examples
;(let ((x (delay (+ 1 2))))
; (for ([i (in-range 1 10)])
; (display (force x))))
;
(define (integers-starting-from n)
(cons-stream n (integers-starting-from (+ n 1))))
(define integers
(integers-starting-from 1))
;(display-line (stream-ref integers 0))
(let ((x (stream-subseq integers 10000 10010)))
(display-stream x))
(define odd-numbers
(stream-filter odd? integers))
(display-stream (stream-subseq odd-numbers 50 60))
;(let ((x (cons-stream 1 (cons-stream 2 '(3)))))
; (display-stream x))
(define (stream-add s n)
(stream-map (lambda (x)
(+ x n)) s))
(define (add-streams s1 s2)
(stream-map + s1 s2))
(define fib
(cons-stream 1
(cons-stream 1
(add-streams fib
(stream-cdr fib)))))
(display-stream (stream-subseq fib 150 160))
(define (divisible? x y)
(= (remainder x y) 0))
(divisible? 10 2)
(define (sieve stream)
(cons-stream
(stream-car stream)
(sieve (stream-filter
(lambda (x)
(not (divisible? x (stream-car stream))))
(stream-cdr stream)))))
(define primes
(sieve (integers-starting-from 2)))
(display-stream (stream-subseq primes 1000 1010))
接下来打算认真体会一下书中提到的欧拉发明的序列加速器的算法。真的是很厉害的 idea.