EcmaScript 2015引入的generator

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力求简洁,

另一个分支正在进行中,为了工业应用。


追求完美,并不是要增加特性,

而是要尽量减少特性。


简洁有效,

包含了太深刻的内涵。