Posted 2021-12-09 21:39:32 GMT
Lisp一人 Advent Calendar 2021 10日目の記事です。
普段利用している便利構文として、Gaucheの~
が元になったsrfi-123の~
/ref
のCommon Lisp版を定義して利用しているのですが、ref
の方にはデフォルト値が指定できるのですが、ref
の便利表記マクロである~
では指定ができません。
何故かというと複数引数の場合には、多段のref
に展開するからなのですが、これはこれで便利です。
(~ place 0 1 2)
===>
(ref (ref (ref place 0) 1) 2)
色々考えましたが、デフォルト値指定の慣用句である、or
と組合せた場合にデフォルト値付きの展開になれば良いのではないか、ということで試してみました。具体的には、setf
の場所としての
(or (~ tab 'a) default)
のようなフォームが、
(ref tab 'a default)
と展開されればOKです。
(setf or)
の定番定義(setf or)
はANSI CL規格では定められていないので、サポートされているとすれば処理系の独自拡張になりますが、CLISPが(setf if)
をサポートしているのでif
の組合せで表現できるor
も結果としてサポートされることになります。
CLISPの場合は、or
の返り値の場所がsetf
の場所となるようです。
つまり、
(or place place place)
という風になります。
今回の場合は、or
を含めてplaceとしたいので、別途定義する必要がありそうです。
ということで、こんな風に書いてみました
(define-setf-expander or (place default &environment env)
(multiple-value-bind (temps subforms stores setterform getterform)
(get-setf-expansion place env)
(values temps subforms stores setterform `(,@getterform ,default))))
これで、
(let* ((tab (make-hash-table))
(subtab (make-hash-table)))
(setf (~ tab 'sub) subtab)
(incf (or (~ tab 'sub 'subkey) 0))
(~ tab 'sub 'subkey))
→ 1
t
は、
(let* ((tab (make-hash-table))
(subtab (make-hash-table)))
(let* ((#:g9763 tab) (#:g9764 'sub) (#:|Store-Var-9762| subtab))
(setf_ref #:|Store-Var-9762| #:g9763 #:g9764))
(let* ((#:g9766 (ref tab 'sub)) (#:g9767 'subkey))
(let* ()
(lisp:let ((#:|Store-Var-9765| (+ (ref #:g9766 #:g9767 0) 1)))
(setf_ref #:|Store-Var-9765| #:g9766 #:g9767))))
(ref (ref tab 'sub) 'subkey))
のように展開されます。
■
HTML generated by 3bmd in LispWorks 7.0.0