読者です 読者をやめる 読者になる 読者になる

レガシーコード生産ガイド

私に教えられることなら

Parenscriptのマクロでハマった

ライフゲームを作った時にできたマクロや関数を別ファイルに移して使おうとしたんだけど、動かない。調べてみるとマクロが展開できてないみたいだ。

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defvar *game-code*) (setq *game-code* ()))

(defmacro develop-game (&body body)
  `(mapcar (lambda (code) (push code *game-code*)) (quote ,body)))

(defun deploy (jsfile)
  (with-open-file 
      (stream jsfile 
              :direction :output 
              :if-exists :supersede
              :if-does-not-exist :create)
    (ps-to-stream stream (lisp ps:*ps-lisp-library*))
    (mapcar 
     (lambda (code)
       (ps-to-stream stream (lisp code)))
     (reverse *game-code*))))

(develop-game
  (defmacro -> (&body body)
    "chainのショートカット"
    `(chain ,@body))

  (defun p (obj)
    "console.logのショートカット"
    (-> console (log obj)))

こんなカンジでdevelop-gameで囲んだコードを*game-code*にpushしていき、最後に逆順にして逐次jsファイルに出力だ……!というつもりだったんだけど、調べてみるとpsやps-to-streamでは、同じ評価時でのみマクロが保持されているみたいだ。

(ps
    (defmacro hoge () ... ))

(ps
    (hoge)) ;; マクロではなくhoge();という呼び出しと認識される

そりゃあそうか、と思って、(ps &body body)や(ps-to-stream stream &body body)のbody部に*game-code*を展開しようとしたんだけど、applyじゃ駄目だし、マクロだと定義時に*game-code*が評価されるからnullになるし…???とわけがわからなくなってしまってギブアップ。

落ち着いて考えてみる。

何故展開しないといけないかというと、*game-code*にpushしていくと

> *game-code*
((defun hoge () )
 (defun fuga () ) ...

と全体で一つの関数と呼出となってしまうわけで、全体の一つの流れと認識してくれれば…あれ?prognで囲めばいいんじゃない?

(defun deploy (jsfile)
  (with-open-file 
      (stream jsfile 
              :direction :output 
              :if-exists :supersede
              :if-does-not-exist :create)
    (ps-to-stream stream (lisp ps:*ps-lisp-library*))
    (ps-to-stream stream (lisp (cons 'progn (reverse *game-code*))))))

これでコードが

(progn
    (defun hoge () )
    (defun fuga () ) ...

となって、マクロも無事展開された。

こ、こんな単純なことに1時間近く悩んでいたのか…

感想

  • 超A級LISPERとかにメンターになって欲しい
広告を非表示にする