オブジェクトっぽく書きたい!

javascriptでは変数に対するメソッドを 変数.メソッド で指定できる。メソッドを定義するのは面倒だが、今回はメソッドにこだわる必要はなく、関数でよい。このような書式の方がデータが見やすくなるので、lispでも使えたら便利だろうと思う。

; オブジェクトベースな書式
foo.bar(fizz).buzz(hoge,fuga).magi

; 対応するlisp
(magi (buzz (bar foo fizz) hoge fuga))

上の書式では、fooのデータに対して処理を行うことが明白であるが、下の書式では中心となるデータが何だか良く分からない。そこで、ちょっと細工をしてデータが主体になる書式に対応させたい。lispではリーダマクロを使うとそれが行える。

#[foo bar]
-> (bar foo) ; 基本

#[foo (bar fizz) (buzz hoge fuga) magi]
-> (magi (buzz (bar foo fizz) hoge fuga)) ; 例題

こんな感じに展開される。望むなら、入れ子に使うこともできる。

; 例題
#[foo (bar fizz) #[hoge (buzz fuga)] magi] 

#[foo #[fizz bar] #[hoge (buzz fuga)] magi] 

#[foo #[fizz bar] #[hoge #[fuga buzz]] magi]

このリーダマクロの定義は次のようである。

(defun \[-reader (stream char param)
  (let* ((*readtable* *\[-readtable*))
    (let* ((obj (read-delimited-list #\] stream nil))
           (l (car obj)))
    (dolist (a (cdr obj) l)
      (setq l (if (consp a)
                  (apply 'list (car a) l (cdr a))
                (list a l)))))))

(set-dispatch-macro-character #\# #\[ #'\[-reader)

; set-dispatch-macro-characterの後に定義する。
(defvar *\[-readtable*
  (let ((readtable (copy-readtable)))
    (set-macro-character #\] #'(lambda (stream c) #\]) nil readtable)
    readtable))
; テスト用関数
(defun test (str)
  (read (make-string-input-stream str)))

(test "#[foo bar]")
|
(bar foo)

(test "#[foo (bar fizz) (buzz hoge fuga) magi]")
|
(magi (buzz (bar foo fizz) hoge fuga))

(test "#[foo (bar fizz) #[hoge (buzz fuga)] magi]")
|
(magi (buzz (bar foo fizz) hoge fuga))

(setq x '(1 2 3 (4 (5))))

(progn #[x cadddr cadr car])
|
5

(progn #[x (elt 3) (elt 1) car])
|
5

; マクロなのでsetfで利用可能。
(progn (setf #[x (elt 3) (elt 1) car] t)
  x)
|
(1 2 3 (4 (t)))

; 関数でなくても良い。
'#[1 2 3 4 5 6]
|
(6 (5 (4 (3 (2 1)))))

; 天の邪鬼な使い方。
#[(#[9 x] #['- f])
  #[#[f (funcall x 3)] let]]
|
6