elispのlambdaにもcalleeを

いろいろ準備中です。小ネタで。

JavaScript には arguments.callee という、「今いる関数」が入っている便利なプロパティがあります。

これが非常に便利で、最近elispでも欲しくなってきたのでこう書いてみました。

(defmacro jslambda (args &rest body)
  (let ((argsyms (loop for i in args collect (gensym))))
  `(lambda (,@argsyms)
     (lexical-let (callee)
       (setq callee (lambda( ,@args ) ,@body))
       (funcall callee ,@argsyms)))))

名前がアレですが、とりあえず普通に lambda の代わりとして書けます。

(setq f (jslambda (n) (* n 2)))
(funcall f 10)
=> 20

(pp-macroexpand-expression f)
#'(lambda
    (G83316)
    (lexical-let
        (callee)
      (setq callee
            (lambda
              (n)
              (* n 2)))
      (funcall callee G83316)))

変数 callee が今いる関数にレキシカルにバインドされていますので、無名のまま再帰が書けます。

(funcall ; フィボナッチ 
 (jslambda (n) 
           (if (>= 1 n) n 
             (+ (funcall callee (- n 1)) 
                (funcall callee (- n 2)))))
 10)
=> 55

JSで非同期を含むGUIプログラムでは、非同期待ち合わせなどで以下のような処理を書くことがあるのですが、そういうときに便利ですねということでした。

//JavaScriptの場合
setTimeout( function() { 
        if (finished) nextFunction();
        else setTimeout(arguments.callee, 100);
    }, 100);
;;elispの場合
(run-at-time 
  0.1 nil (jslambda ()
          (if finished (next-function)
            (run-at-time 0.1 nil callee))))