標高+1m

Don't be rational.

プチ発明 Lispマクロ with-many

Lispマクロの話です。Lispという言語は、プログラム自体がリストで記述されているので、そのリストを操作することで構文を自由*1に作ることができるのです。

LispでCGを描きたいぞと思って、これまで色々と試してきたが、どうも人の作った環境は複雑で、エラーが起きた時にどこを直せばいいのかわからなかったりすると思った。
これはどうも自分で何もないところから始めて、必要になったら機能毎に取り込んでいった方が自由に描けそうだと思って、Common LispのCL-SDL2から始めることにした。

Lispgames製のCL-SDL2は、基本的にはFFIでSDL2を呼び出すだけの薄いラッパーみたいなのだけど、SDL2はCなのでメモリー管理を自分でする必要があって、そこの面倒くささを和らげるために with-* 系のマクロがいくつも用意されている。これを使えばunwind-protectで自動的に破棄してくれる仕組み。

例えば、

;; excerpt from cl-sdl2/src/video.lisp
(defmacro with-window ((win
                        &key (title "SDL2 Window") (x :centered) (y :centered) (w 800) (h 600) flags)
                       &body body)
  `(let ((,win (create-window :title ,title :x ,x :y ,y :w ,w :h ,h :flags ,flags)))
     (unwind-protect (progn ,@body)
       (destroy-window ,win))))

しかしこれを使うと、

(sdl2:with-init (:everything)
  (sdl2:with-window (win :w 800 :h 600 :title "")
    (sdl2:with-renderer (renderer win)
      "do something")))

と、どんどんネストが深くなっていってしまう。そこでこんなマクロを書いた。

(defmacro with-many (w-clauses &body body)
  (if (null w-clauses)
      `(progn ,@body)
      (append (car w-clauses)
          `((with-many ,(cdr w-clauses) ,@body)))))

簡単に言うと、
(with-many ((a ... ) (b ... ) (c ... )) ... ) -> (a ... (b ... (c ... ...)))
こんな感じ。

これを使うと、

(defun main ()
  (with-many
      ((sdl2:with-init (:everything))
       (sdl2:with-window (win :flags '(:shown)
                  :w 1800 :h 600 :title ""))
       (sdl2:with-renderer (renderer win :flags '(:ACCELERATED)))
       (let1 texs (collect-textures renderer
                    "resources/images/")))
    "do something"))

こんな風に複数の with-*letwhen などをフラットに書けるのであーる。

let*に似ている。

簡単なExampleをgistに置いた。ディレクトリ構造を適当に作って画像を置けば動かせるはず。

Load and draw image with CL-SDL2 · GitHub

CL-SDL2 running under WSLg
CL-SDL2 running under WSLg

参考: SDL2の使い方 - mirichiの日記

ではまた。

P.S. そういえば、今回はWindows 11でWSL2を初めて使ってみたが、素晴らしい。Windows 11はLinuxランチャーだ。

*1:制約がないという意味ではない