#:g1: デフォルト値付きのsetf

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)という風になります。

デフォルト値指定ありのsetfの場所

今回の場合は、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

comments powered by Disqus