y2q_actionman’s ゴミクズチラ裏

内向きのメモ書きを置いてます

ファイルの末尾まで利くannotation

昔に記事にもした cl-annot-revisit というライブラリを、ちょっと前に Quicklisp に収録 してもらった。

github.com

なんか空いていた Lisp Advent Calendar 2022 の記事として、 cl-annot-revisit に搭載したしょうもない機能について書いておこうと思う。

@ リーダーマクロ

cl-annot-reivisit は、他の言語でよくある @ によるアノーテーションっぽく見える記法を導入するライブラリである。

@ リーダーマクロは、 @ 直後にある form の末尾に、 その次にある form を1つ 追加する。 例えば以下のように書くと:

@(eval-when (:compile-toplevel :load-toplevel :execute))
(defun hello ()
  "Hello, World!")

以下のように、 eval-when の中に defun が展開される。

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defun hello ()
    "Hello, World!"))

@ リーダーマクロがやってるのはこの並び替えだけで、各 form をどう扱うのかは @ の展開後のオペレータに丸投げしている。

別に toplevel を特別扱いしていないので、関数の中で使ってもいい:

(defun hello-string (name)
  @(with-output-to-string (stream))
  (format stream "Hello, ~A!" name))

(hello-string "hoge") ; => "Hello, hoge!"

また、展開結果がマクロである必要もなく、関数呼び出しになってもよい。 あと略記法として、 @ の直後にシンボルが現れた場合は、そのシンボルをオペレータと思って呼び出すことにしている:

(defun inverse-number (n)
  ;; 関数 `cl:/' の呼び出し。 (/ n) と等価。
  @/ n)

(inverse-number 100) ; => 1/100

何個 form を取ってもいいよね

@ リーダーマクロは、 その次にある form を1つ 追加するものであった。

でも 別に1個に限る必要もない ように感じられる。そこで、 dispatching macro char なら整数引数を取れることを使い、追加する form の数を指定できるようにした #n@ リーダーマクロを用意した。

以下は二つの form に作用する例:

#2@(eval-when (:compile-toplevel :load-toplevel :execute))
(defun hello-world-1 ()
  "Hello, World! (1)")

(defun hello-world-2 ()
  "Hello, World! (2)")

これは、以下のように展開される。

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defun hello-world-1 ()
    "Hello, World! (1)")
  (defun hello-world-2 ()
    "Hello, World! (2)"))

&body を取るマクロや &rest を取る関数には便利かもしれない。 (しかし () で括った方が自明だろう。)

EOF まで作用してもいいよね

これまでのリーダーマクロは個数を指定していたが、 別に何個読んでもいい のかなと思った。

そこで、 #@ リーダーマクロを整数引数を渡さずに呼んだ場合、 EOF か ) が出るまで read して括るようにしてみた。 例えば以下のように書くと、冒頭の eval-when を EOF まで作用させることが出来る。

#@(eval-when (:compile-toplevel :load-toplevel :execute))

(defun hello-world-1 ()
  "Hello, World! (1)")

(defun hello-world-2 ()
  "Hello, World! (2)")

(defun hello-world-3 ()
  "Hello, World! (3)")

また、 ) まで集めることを利用して別の form の一部で作用することも出来る:

(string= "abcABC123"
         ;; 以下は (concatenate 'string "abc" "ABC" "123") と等価
         #@(concatenate 'string)
         "abc"
         "ABC"
         "123") ; => T

用途はない

実際のところ、素直に括弧でくくればいいところ、 こんなリーダーマクロを使ってコードを書く必要がない と思うので、用途はないと思う。

なんかノリで実装しただけの機能を紹介するという記事でした。