Racket, SICP stream learning
在 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.