Ecmascript 2015刚刚发布了,
新增了很多特性,
例如,let,promise,iterator,generator,
proxy,class,module等等。
其中generator更是引人注目,
可以便捷的产生一个iterator。
function* gen() {
yield 1;
yield 2;
return 3;
}
var i = gen();
console.log(i.next().value); //1
console.log(i.next().value); //2
console.log(i.next().value); //3
console.log(i.next().value); //undefined
可见,每次调用i.next,
gen都会从上一次的yield后继续执行。
这在Ecmascript之前版本看来,
是不可思议的。
用call/cc实现
从Scheme的角度来看,
从断点处继续执行,
其实就是,调用yield断点处的continuation。
所以,yield并不是一个新概念,
我们用call/cc实现一下。
(define (gen x)
(define k-body #f)
(define k-yield #f)
(define (yield x)
(call/cc (lambda (k2)
(set! k-yield k2)
(k-body x))))
(lambda ()
(call/cc (lambda (k1)
(if (eq? k-body #f)
(begin
(set! k-body k1)
(yield x)
(yield (+ x 1))
(+ x 2))
(k-yield))))))
(define i (gen 1))
(i) ;1
(i) ;2
(i) ;3
(i) ;3
仅最后一次调用与Ecmascript不同。
无伤大雅。
用macro进行抽象
像上面那样为每一个generator写call/cc,
太繁琐了,
Scheme程序员是不能容忍的。
先看看我们希望怎样调用,
(make-generator (gen x y)
(yield x)
(yield y)
(+ x y))
(define i (gen 1 2))
(i) ;1
(i) ;2
(i) ;3
(i) ;3
这样就清爽多了,
还把generator改成了可接收多参数的形式。
make-generator是一个宏,它可以用来定义generator。
具体实现如下,
(define-syntax make-generator
(lambda (form)
(syntax-case form ()
[(keyword (name ...) . body)
(syntax-case (datum->syntax #'keyword 'yield) ()
[yield
#'(define (name ...)
(define k-body #f)
(define k-yield #f)
(define (yield . args)
(call/cc (lambda (k2)
(set! k-yield k2)
(apply k-body args))))
(lambda ()
(call/cc (lambda (k1)
(if (eq? k-body #f)
(begin
(set! k-body k1) . body)
(k-yield))))))])])))
结语
Ecmascript 2015加入了很多特性,
把自己搞的更加复杂了,
这在走一些臃肿语言的老路。
Scheme也不例外,
R6RS就是膨胀的版本,
不过还好,从那之后,
语言分为了两个分支,R7RS力求简洁,
另一个分支正在进行中,为了工业应用。
追求完美,并不是要增加特性,
而是要尽量减少特性。
简洁有效,
包含了太深刻的内涵。