Emacs Lisp 生成器 (Generators)

(info "(elisp) Generators")


以无限长的斐波那契数列为例,分别用 Python 和 Emacs Lisp 的生成器实现:

Python

def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

# 前 10 个斐波那契数
for i, n in enumerate(fibonacci()):
    if i == 10: break
    print(n, end=" ")
# 0 1 1 2 3 5 8 13 21 34

Emacs Lisp

(iter-defun fibonacci ()
  (let ((a 0)
        (b 1))
    (while t
      (iter-yield a)
      (cl-psetq a b
                b (+ a b)))))

;; 前 10 个斐波那契数
(cl-loop repeat 10
         for n iter-by (fibonacci)
         collect n)
;; => (0 1 1 2 3 5 8 13 21 34)
5 个赞

额 报错了

1 Debugger entered--Lisp error: (cl-assertion-failed (lexical-binding nil))                                                                                                                                         
 2   cl--assertion-failed(lexical-binding)                                                                                                                                                                           
 3   #[642 "^H\204^H^@\301\300!\210\302^A!\[email protected]^AA\303^F^F^F^F\304^E\305^F^F!C\"BBB\207" [lexical-binding cl--assertion-failed macroexp-parse-body defun append cps-generate-evaluator] 13 ("/usr/share/emacs/25.3/l$
 4   macroexpand((iter-defun fib nil (let ((a 0) (b 1)) (while t (iter-yield a) (cl-psetq a b b (+ a b))))) nil)                                                                                                     
 5   macroexp-macroexpand((iter-defun fib nil (let ((a 0) (b 1)) (while t (iter-yield a) (cl-psetq a b b (+ a b))))) nil)                                                                                            
 6   macroexp--expand-all((iter-defun fib nil (let ((a 0) (b 1)) (while t (iter-yield a) (cl-psetq a b b (+ a b))))))                                                                                                
 7   macroexpand-all((iter-defun fib nil (let ((a 0) (b 1)) (while t (iter-yield a) (cl-psetq a b b (+ a b))))))                                                                                                     
 8   eval-sexp-add-defvars((iter-defun fib nil (let ((a 0) (b 1)) (while t (iter-yield a) (cl-psetq a b b (+ a b))))))                                                                                               
 9   elisp--eval-last-sexp(nil)                                                                                                                                                                                      
10   eval-last-sexp(nil)                                                                                                                                                                                             
11   funcall-interactively(eval-last-sexp nil)                                                                                                                                                                       
12   call-interactively(eval-last-sexp nil nil)                                                                                                                                                                      
13   command-execute(eval-last-sexp)                                                                                                                                                                                 
14   recursive-edit()                                                                                                                                                                                                
15   debug(error (cl-assertion-failed (lexical-binding nil)))                                                                                                                                                        
16   cl--assertion-failed(lexical-binding)                                                                                                                                                                           
17   #[642 "^H\204^H^@\301\300!\210\302^A!\[email protected]^AA\303^F^F^F^F\304^E\305^F^F!C\"BBB\207" [lexical-binding cl--assertion-failed macroexp-parse-body defun append cps-generate-evaluator] 13 ("/usr/share/emacs/25.3/l$
18   macroexpand((iter-defun fib nil (let ((a 0) (b 1)) (while t (iter-yield a) (cl-psetq a b b (+ a b))))) nil)                                                                                                     
19   macroexp-macroexpand((iter-defun fib nil (let ((a 0) (b 1)) (while t (iter-yield a) (cl-psetq a b b (+ a b))))) nil)                                                                                            
20   macroexp--expand-all((iter-defun fib nil (let ((a 0) (b 1)) (while t (iter-yield a) (cl-psetq a b b (+ a b))))))                                                                                                
21   macroexpand-all((iter-defun fib nil (let ((a 0) (b 1)) (while t (iter-yield a) (cl-psetq a b b (+ a b))))))                                                                                                     
22   eval-sexp-add-defvars((iter-defun fib nil (let ((a 0) (b 1)) (while t (iter-yield a) (cl-psetq a b b (+ a b))))))                                                                                               
23   elisp--eval-last-sexp(nil)                                                                                                                                                                                      
24   eval-last-sexp(nil)                                                                                                                                                                                             
25   funcall-interactively(eval-last-sexp nil)                                                                                                                                                                       
26   call-interactively(eval-last-sexp nil nil)                                                                                                                                                                      
27   command-execute(eval-last-sexp)                                                                                                                                                                                 
28   recursive-edit()                                                                                                                                                                                                
29   debug(error (cl-assertion-failed (lexical-binding nil)))                                                                                                                                                        
30   cl--assertion-failed(lexical-binding)                                                                                                                                                                           
31   #[642 "^H\204^H^@\301\300!\210\302^A!\[email protected]^AA\303^F^F^F^F\304^E\305^F^F!C\"BBB\207" [lexical-binding cl--assertion-failed macroexp-parse-body defun append cps-generate-evaluator] 13 ("/usr/share/emacs/25.3/l$
32   macroexpand((iter-defun fib nil (let ((a 0) (b 1)) (while t (iter-yield a) (cl-psetq a b b (+ a b))))) nil)                                                                                                     
33   macroexp-macroexpand((iter-defun fib nil (let ((a 0) (b 1)) (while t (iter-yield a) (cl-psetq a b b (+ a b))))) nil)                                                                                            
34   macroexp--expand-all((iter-defun fib nil (let ((a 0) (b 1)) (while t (iter-yield a) (cl-psetq a b b (+ a b))))))                                                                                                
35   macroexpand-all((iter-defun fib nil (let ((a 0) (b 1)) (while t (iter-yield a) (cl-psetq a b b (+ a b))))))                                                                                                     
36   eval-sexp-add-defvars((iter-defun fib nil (let ((a 0) (b 1)) (while t (iter-yield a) (cl-psetq a b b (+ a b))))))                                                                                               
37   elisp--eval-last-sexp(nil)                                                                                                                                                                                      
38   eval-last-sexp(nil)                                                                                                                                                                                             
39   funcall-interactively(eval-last-sexp nil)                                                                                                                                                                       
40   call-interactively(eval-last-sexp nil nil)                                                                                                                                                                      
41   command-execute(eval-last-sexp)                                                                                                                                                                                 
42   recursive-edit()                                                                                                                                                                                                
43   debug(error (cl-assertion-failed (lexical-binding nil)))                                                                                                                                                        
44   cl--assertion-failed(lexical-binding)                                                                                                                                                                           
45   #[642 "^H\204^H^@\301\300!\210\302^A!\[email protected]^AA\303^F^F^F^F\304^E\305^F^F!C\"BBB\207" [lexical-binding cl--assertion-failed macroexp-parse-body defun append cps-generate-evaluator] 13 ("/usr/share/emacs/25.3/l$
46   macroexpand((iter-defun fib nil (let ((a 0) (b 1)) (while t (iter-yield a) (cl-psetq a b b (+ a b))))) nil)                                                                                                     
47   macroexp-macroexpand((iter-defun fib nil (let ((a 0) (b 1)) (while t (iter-yield a) (cl-psetq a b b (+ a b))))) nil)                                                                                            
48   macroexp--expand-all((iter-defun fib nil (let ((a 0) (b 1)) (while t (iter-yield a) (cl-psetq a b b (+ a b))))))                                                                                                
49   macroexpand-all((iter-defun fib nil (let ((a 0) (b 1)) (while t (iter-yield a) (cl-psetq a b b (+ a b))))))                                                                                                     
50   eval-sexp-add-defvars((iter-defun fib nil (let ((a 0) (b 1)) (while t (iter-yield a) (cl-psetq a b b (+ a b))))))                                                                                               
51   elisp--eval-last-sexp(nil)                                                                                                                                                                                      
52   eval-last-sexp(nil)                                                                                                                                                                                             
53   funcall-interactively(eval-last-sexp nil)                                                                                                                                                                       
54   call-interactively(eval-last-sexp nil nil)                                                                                                                                                                      
55   command-execute(eval-last-sexp)                                                                                                                                                                                 
56   recursive-edit()                                                                                                                                                                                                
57   debug(error (cl-assertion-failed (lexical-binding nil)))                                                                                                                                                        
58   cl--assertion-failed(lexical-binding)                                                                                                                                                                           
59   #[642 "^H\204^H^@\301\300!\210\302^A!\[email protected]^AA\303^F^F^F^F\304^E\305^F^F!C\"BBB\207" [lexical-binding cl--assertion-failed ma

code:

(require 'generator)
(iter-defun fib ()
  (let ((a 0)
	(b 1))
    (while t
      (iter-yield a)
      (cl-psetq a b
		b (+ a b)))))

lexical-binding 设为 t

在文首:

;;; test-fibonacci.el --- Test fibonacci -*- lexical-binding: t; -*-

或者

(let ((lexical-binding t))
  ;; do something
  )

iter-defun 展开看了一下,好家伙,100 多行,20 多个 lambda。

然后再对比其它实现,各做了 1000 次的 fibonacci(100) 测试,生成器在书写上最直观,但效率是最低的:

⋊> emacs-nightly --batch -l ~/.scratch/elisp/test-fibonacci.el
fibonacci1 [generator]: 0.847885s
fibonacci2 [iteration]: 0.319813s
fibonacci3 [recursion]: 0.565710s
fibonacci4 [generat0r]: 0.175119s

不过这测试也不完全公平,因为其中的递归版本 fibonacci3 没有收集计算过程中的值。循环版本 fibonacci2 增加了回调以收集计算过程中的值,算是稍微增加了一点公平性。fibonacci4 则是模拟生成器效果的一个实现:

(defvar fibonacci4--a 0)
(defvar fibonacci4--b 1)
(defun fibonacci4 ()
  (let ((a fibonacci4--a))
    (setq fibonacci4--a fibonacci4--b 
          fibonacci4--b (+ a fibonacci4--b))
    a))

(dotimes (_ 1000)
  (setq fibonacci4--a 0)
  (setq fibonacci4--b 1)
  (cl-loop repeat 100
           for n = (fibonacci4)
           collect n))

完整测试代码:https://gist.github.com/twlz0ne/c17c936340b89bf1af282fed6303416c

除了 @twlz0ne 提到的办法,一般测试时,在需要的 Buffer (对我来说一般是 *scratch* )中 (setq lexical-binding t) 也行。

(setq lexical-binding nil)
;; => nil

(let ((x 123)) (defun foo () x))
;; => foo

(foo)
error-> Symbol's value as variable is void: x



(setq lexical-binding t)
;; => t

(let ((x 123)) (defun foo () x))
;; => foo

(foo)
;; => 123

哎,少加了个q……
(setq lexical-binding t)
ok了