deferred.el であそんでみる

deferred:loop を接続したいと思っていろいろやってみた。

ためしみた

最初に書いたのがこれ。

(deferred:$
  (deferred:loop (loop for x from 0 to 1000 collect x)
    (lambda () 
      (message "%s" x)))
  (deferred:loop (loop for x from 1000 downto 0 collect x)
    (lambda () 
      (message "%s" x)))
  (deferred:nextc it
    (lambda ()
      (message "end"))))

うまくいかない。最初のloopと次のloopが一緒に走ってる感じ。次に deferred:nextc で loop を囲んでみた。

(deferred:$
  (deferred:loop (loop for x from 0 to 1000 collect x)
    (lambda (x) 
      (message "%s" x)))
  (deferred:nextc it
    (lambda ()
      (deferred:loop (loop for x from 1000 downto 0 collect x)
        (lambda (x) 
          (message "%s" x)))))
  (deferred:nextc it
    (lambda ()
      (message "end"))))

これだとうまくいった。deferred:loop は前の deferred と接続できないから、deferred:nextc で開始を遅延させる。

しらべてみた

微妙に不思議なのが 2回目の loop が終わってから "end" ってメッセージがでること。2回目の loop がはじまったらそのまま deferred:nextc が終わってすぐに "end" って出るんじゃないかと思った。

そこで deferred:nextc の実装を見てみた。deferred:nextc は簡単。 callback を呼び出す deferred を作って 引数の d と繋ぐだけ。

(defun deferred:nextc (d callback)
  "Create a deferred object with OK callback and connect it to the given deferred object."
  (let ((nd (make-deferred :callback callback)))
    (deferred:set-next d nd)))

ここから呼ばれてる deferred:set-next で目的の処理をしてた。deferred:exec-task の返り値が deferred ならそれを返り値としてる。

(defun deferred:set-next (prev next)
  "[internal] Connect deferred objects."
...
    (let ((ret (deferred:exec-task
                 next 'ok (deferred-value prev))))
      (if (deferred-p ret) ret
        next))
...)

RAEDME の「実行時に接続」とか「lambdaの返り値に気を使う」は多分これのこと。
やっとなっとくした。

もうちょっとあそんでみた

まだほとんど使ってないけど、deferred:$ を使うときって大体 deferred:nextc で接続するんじゃないかと思う。そう思って引数の関数を deferred として順次実行する deferred:sequential ってのを作ってみた。

(defun deferred:sequential (&rest args)
  (deferred:loop args 'funcall))

これでさっきの loop のやつはこんな風に書ける。少しみやすくなった。

(deferred:sequential
  (lambda ()
    (deferred:loop (loop for x from 0 to 1000 collect x)
      (lambda (x) 
        (message "%s" x))))
  (lambda ()
    (deferred:loop (loop for x from 1000 downto 0 collect x)
      (lambda (x) 
        (message "%s" x))))
  (lambda ()
    (message "end")))

lambda を書くのが面倒ならこんなのを定義するのもいいかもしれない。

(defmacro deferred:$$ (&rest elements)
  (lexical-let ((elements elements))
    `(deferred:sequential
       ,@(loop for i in elements
               collect `(lambda () ,i)))))

これを使うと lambda を書かなくてよくなる。やりすぎっぽけど。

(deferred:$$
  (deferred:loop (loop for x from 0 to 1000 collect x)
    (lambda (x) 
      (message "%s" x)))
  (deferred:loop (loop for x from 1000 downto 0 collect x)
    (lambda (x) 
      (message "%s" x)))
  (message "end"))

いろいろためしてみたけど

test-deferred.el を見るのが一番はやく理解できるんじゃないかと思った。というわけで deferred.el を使いたい人は test-deferred.el を見るのがいいと思います。