lispでドット表記関数呼び出し
一般的なオブジェクト指向言語では、メソッドを、インスタンス(オブジェクト)とメソッドの間にドット(.)をつけた表記で呼び出せることが多い。
# メソッド呼び出し object.method # ドットを繋げて、複数のメソッド呼び出し object.methodA.methodB
lispでコードを書いていると、たまにドット表記で関数を呼び出したくなることがあるので、似たようなことをするリードマクロを書いてみる。
; ※ nlet-acc-revの定義は末尾に掲載 ;; 文字列分割: 若干非効率? (defun split-by-char (chr str) (nlet-acc-rev self ((i (1- (length str))) (tmp '())) (if (= i -1) #1=(accumulate (coerce tmp 'string)) (if (char= chr #2=(char str i)) (self (1- i) (progn #1# '())) (self (1- i) (cons #2# tmp)))))) ;; ドット表記の関数呼び出しを、S式に変換 ;; ※'(list 1 2 3).car'の様な、先頭がconsの呼び出しには未対応 ;; ※二つ以上の引数を取る関数の呼び出しにも未対応 (defun read-dot-exprs (stream) (reduce (lambda (arg fn) `(,fn ,arg)) (mapcar #'intern (split-by-char #\. (symbol-name (read stream)))))) ;; リードマクロ読み込み (set-macro-character #\@ (lambda (stream c) (declare (ignore c)) (read-dot-exprs stream)))
テスト
> (defvar lst '(1 2 3 4 5)) > @lst.car --> 1 > @lst.cdr.list --> ((2 3 4 5)) > @lst.rest.rest.second.sqrt --> 2.0
だいたい、こんなものかな。
ただ、書いてみたのはいいけど、わざわざマクロ文字を一つ割り当てるほど必要ではない気がする。
マクロ定義
上のコード内で使っているnlet-acc-rev(とこれが依存する関数・マクロ)の定義
; ※以下の二つの出典は、確か『LET OVER LAMBDA』 (defun formalize-letargs (args) (mapcar (lambda (a) (if (atom a) (list a) a)) args)) (defmacro nlet (fn-name letargs &body body) (setf letargs (formalize-letargs letargs)) `(labels ((,fn-name ,(mapcar #'car letargs) ,@body)) (,fn-name ,@(mapcar #'cadr letargs)))) ;; nletにaccumulateローカル関数を追加 ;; accumulateで(accに)蓄積されたものを、最後にlistとして返す ;; 返されるlist内の要素の順番は、accumulateされた順番の逆(スタック順) (defmacro nlet-acc-rev (fn-name letargs &body body) (let ((acc (gensym))) `(let ((,acc '())) (flet ((accumulate (x) (push x ,acc))) (nlet ,fn-name ,letargs ,@body)) ,acc)))