きくらげ観察日記

好きなことを、適当に。

Gaucheでセッターを定義する

Gaucheではデータ構造の特定の値を変更するのに、

(set! (~ vector i) 10)

とか

(set! (cdr pair) ())

とかできてしまうのが不思議だったので、方法を調べてみました。

まずは例として、以下のようなクラスを作ってみます。

(define-class <temperature> ()
  ([celsius :init-keyword :celsius]))

(define (celsius->temperature c)
  (make <temperature> :celsius c))

(define (fahrenheit->temperature k)
  (make <temperature> :celsius (k->c k)))

(define (celsius temp)
  (~ temp 'celsius))

(define (fahrenheit temp)
  (c->k (~ temp 'celsius)))



;; helper functions
(define (k->c k)
  (* 5/9 (- k 32)))

(define (c->k c)
  (+ 32 (* 9/5 c)))

これは単位に関係なく温度を扱うことのできるクラスです。

(define t (celsius->temperature 32))
(fahrenheit t) ; => 448/5

(define t1 (fahrenheit->temperature 50))
(celsius t1) ; => 10

このクラスにセッターを定義してみましょう。まずは原型から。

(define (set-celsius! temp c)
  (set! (~ temp 'celsius) c))
(define (set-fahrenheit! temp k)
  (set! (~ temp 'celsius) (k->c k)))

これでも充分ですが、どうせなら組み込み型と同じように

(set! (celsius temp) 10)

のようにできたらかっこいいですよね。

これを行うためのメソッドがsetterです。

https://practical-scheme.net/gauche/man/gauche-refj/Dai-Ru-.html

リファレンスだと少々わかりにくいような気がするのですが、setterメソッドを定義してやればset!等で使えるようになるようです。

(define-method (setter celsius) ((t <temperature>) c)
  (set-celsius! t c))
(define-method (setter fahrenheit) ((t <temperature>) k)
  (set-fahrenheit! t k))

(set! (celsius t) 10)
(fahrenheit t) ; => 50
(set! (fahrenheit t) 32)
(celsius t) ; => 0

ちなみに、

(define-method (setter celsius) (...) ...)

は特にsetter用の特殊構文などではなく、一般に

(define ((f x) y z ...) ...)

のような書き方は

(define (f x) (lambda (y z ...) ...))

シンタックスシュガーとなっています。

また、総称関数refとそのsetterを定義すると、万能アクセサ~を使った

(set! (~ hoge x) y)

というような記法が使えるようになります。