先日、弊社の新サービス「ポケペイ (Pocket Change Pay)」のランディングページが公開されました。
ポケペイは独自電子マネーを作れるプラットフォームです。プラットフォーム内で換金 (Exchange) もできる仕組みはポケチェらしさがあります。
去年転職して からCommon Lispで仕事してるとは言うけれども、具体的に何をしてるのか聞かれると言えないもどかしさがありましたが、これで言えます。これを作ってます。
APIなどサーバーサイドと、チャージ用端末のクライアントアプリにCommon Lispが使われています。
ポケペイの話はまたいつかするとして、今日は小ネタです。
Common Lispのリーダーマクロ
Common Lispには「リーダーマクロ」という機能があります。
リーダーマクロとは、プログラムのリード時に特定の文字に応じてフックして好きな処理を入れることができる機能です。フックする処理はCommon Lisp自身で書くことができます。これにより好きなシンタックスを作ることができます。
具体例をあげるとCommon Lispにアノテーションを導入する「cl-annot」とか。
@export (defun foobar () ...)
@
という文字をフックして後に続くフォームと組み合わせてS式に展開します。
(export (defun foobar () ...))
いわゆる「マクロ」というのはコンパイル時ですが、これはそれよりも早いパース時に実行される点が違います。
この cl-annot は、アノテーションの定義もすることができるので、軽量Webフレームワークの「Caveman2」にはルーティング用のアノテーションがあったりします。
@route GET "/" (defun index () (render #P"index.tmpl")) @route GET "/hello" (defun say-hello (&key (|name| "Guest")) (format nil "Hello, ~A" |name|))
マクロでも同様の「機能」はできますが、「修飾」という意図を明確にするという意味で気に入って使っています。理由の詳細は昔書いた記事をご覧ください。
ASDFシステム全体で共通のreadtableを使う
このリーダーマクロは使いようによって便利ではあるのですが、いちいちファイルの先頭で (syntax:use-syntax :annot)
とか書かないといけないのが面倒です。使うときにも、「このファイルでannot有効になってたっけ…」と確認しないと使えません。
これは特にパッケージごとにファイルを分割する package-inferred-system と相性が悪いです。
そもそもreadtableのようなシンタックスに関わるようなものは、プロジェクトメンバーの全員の合意が必要なものですし、ファイルごとにシンタックスを変えたいという需要も少ないように思います。
そこで、ASDFシステム全体でcl-annotが有効になった共通のreadtable使うようにすればいいんじゃね?と思ってやってみました。
ASDF :around-compile
(defsystem "pokepay-server" :class :package-inferred-system :version "0.1" :author "Pocket Change, Inc." :description "Pokepay Project API & Admin site." :depends-on ("pokepay-server/boot" "cl-syntax") + :around-compile (lambda (thunk) + (uiop:symbol-call :cl-syntax :%use-syntax :annot) + (uiop:symbol-call :cl-syntax :%use-syntax :interpol) + (funcall thunk)) :in-order-to ((test-op (test-op pokepay-server/tests))))
:around-compile
内で cl-syntax:use-syntax
しているだけです。上記の例ではついでにinterpolも有効にしています。
こうしておけばASDFのシステム内のファイル全部で自動的にアノテーションと文字列インターポレーションが有効になります。
まとめ
リーダーマクロをASDFシステム全体で有効にする方法を紹介しました。実際に弊社のアプリケーションで使っています。