ロカール関数で (setf READER) を諦めた記録
CL だとこんなことができる。
;; Clozure CL での例 CL-USER> (let ((table (make-hash-table))) (labels ((key (key) (gethash key table)) ((setf key) (value key) (setf (gethash key table) value))) (setf (key 1) "foo" (key 2) "bar") (maphash (lambda (k v) (format t "~S -> ~S~%" k v)) table) (values (key 1) (key 2)))) 2 -> "bar" 1 -> "foo" "foo" "bar"
(setf (key X) VALUE) を正しく展開できるようにするにはこんなのがあればいい。
(defun |(setf key)| (value key) (setf (gethash key table) value)) => |(setf key)| (define-setf-method key (&rest sub-forms) (values (list '#1=#:args) (list `(list ,@sub-forms)) (list '#2=#:new-value) `(apply '|(setf key)| #2# #1#) `(apply 'key #2#))) => (setf key) (macroexpand '(setf (key X) VALUE)) => (let* ((#1=#:args (list X)) (#2=#:new-value VALUE)) (apply '|(setf key)| #2# #1#)) => t
define-setf-method で定義した setf-expander はプロパティ lisp::setf-method にある。
(get 'key 'lisp::setf-method) => #<lexical-closure: (anonymous)> (si:closure-body *) => (lambda (#:G225 &rest sub-forms) (values (list '#1=#:args) (list (cons 'list sub-forms)) (list '#2=#:new-value) '(apply '|(setf key)| #2# #1#) '(apply 'key #2#)))
labels の BODY 部分で setf を(macrolet で)置き換えて、展開する時に一時的にプロパティ lisp::setf-method に setf-expander を突っ込んでやる。のだが展開できない。
(remprop 'x 'lisp::setf-method) => nil ;; 展開前の式 (let ((table (make-hash-table))) (labels ((key (key) (gethash key table)) ((setf key) (value key) (setf (gethash key table) value))) (setf (key 1) "foo" (key 2) "bar") (maphash (lambda (k v) (format t "~S -> ~S~%" k v)) table) (values (key 1) (key 2)))) ;; 手動ででっちあげた展開形 (let ((table (make-hash-table))) (labels ((key (key) (gethash key table)) (|(setf key)| (value key) (setf (gethash key table) value))) (macrolet ((setf (&environment env &rest args) (let ((ovals nil) (expander (lambda (#:env &rest sub-forms) (values (list '#1=#:args) (list `(list ,@sub-forms)) (list '#2=#:new-value) `(apply #'|(setf key)| #2# #1#) `(apply 'key #2#))))) (unwind-protect (progn (push (cons 'key (get 'key 'lisp::setf-method '#0=#:unbound)) ovals) (si:*putprop 'key expander 'lisp::setf-method) ;; lisp/setf.l より (cond ((endp args) nil) ((endp (cdr args)) (error "~S: 不正なsetfフォームです" args)) ((endp (cddr args)) (lisp::setf-expand-1 (car args) (cadr args) env)) (t (cons 'progn (lisp::setf-expand args env))))) (let ((oval (cdr (assoc 'key ovals)))) (if (eq oval '#0#) (remprop 'key 'lisp::setf-method) (si:*putprop 'key oval 'lisp::setf-method))))))) (setf (key 1) "foo" (key 2) "bar") (maphash (lambda (k v) (format t "~S -> ~S~%" k v)) table) (values (key 1) (key 2))))) ; Evaluation aborted on `simple-error' ; (key 1)のsetfメソッドはありません
でどこでエラーになるのか調べたら get-setf-method-multiple-value でローカルな(関数|マクロ)だったらマクロ展開できなきゃいかん事になってる。
;; lisp/setf.l より (defun get-setf-method-multiple-value (form &optional env) (let (tem) (cond ; ...(snip)... ((si:*find-in-environment form env) ;(setq tem (assoc (car form) env)) (setq tem (macroexpand-1 form env)) (if (eq tem form) (error "~Sのsetfメソッドはありません" form)) (get-setf-method-multiple-value tem env)) ; ...(snip)... )))
ここまでやって諦めた。
ローカルな setf で展開する時に一時的に get-setf-method-multiple-value の定義を入れ替えてやれば何とかなるような気がするけど、疲れたので記録だけ残して放置。