一般化set!、その2

確か、121ページまで終わってたはず。
9章6節、「リストの変更と一般化set!」の続き。


HPの取得と変更は、get-itemとadd-item!にならって、次のように書ける。

;; HPの値を取得
(define get-hp
  (lambda (player)
    (cdr (assoc 'hp player))))

;; HPの値を変更
(define add-hp!
  (lambda (player n)
    (let ((p (assoc 'hp player)))
      (set! (cdr p) (+ n (cdr p))))))

で、get-itemとget-hp、add-item!とadd-hp!には共通するパターンがあるので、抽象化。
9章3節「手続きによるパターンの抽象化」(id:yagiey:20090309:1236597824)を参考に考えてみる。


共通する部分を書き出してみると、

(define get-○○
  (lambda (player)
    (cdr (assoc △△ player))))

(define add-○○!
  (lambda (player △△)
    (let ((p (assoc □□ player)))
      (set! (cdr p) (☆☆ △△ (cdr p))))))

てな感じですかね。
add-item!やadd-hp!のような、add-○○!の方は、属性によって変更の仕方が違う。
つまり、☆☆が属性によって違う。
今までの例では

  • delete-1(delete-item!の場合)
  • cons(add-item!の場合)
  • +(add-hp!の場合)

だった。
あとで出てくるけど、positionの更新は現在の値を使わないので、☆☆に相当するものすら不要。
こんな時は「以前の値を渡すと新しい値を返す手続き」を渡してやるといいらしい。

;; 指定したプレイヤーの、指定した属性値を取得
(define get-player-attr
  (lambda (pleyar attr)
    (cdr (assoc attr player))))

;; 指定したプレイヤーの、指定した属性値を変更
(define set-player-attr!
  (lambda (player attr updater)
    (let ((p (assoc attr player)))
      (set! (cdr p) (updater (cdr p))))))

となる。
これを使って、プレイヤーの属性値を取得したり変更したりする手続きを以下のように書ける。

;; 持ち物リストを取得
(define get-inventory
  (lambda (player)
    (get-player-attr player 'inventory)))

;; 持ち物リストにアイテムを追加する
(define add-item!
  (lambda (player item)
    (set-player-attr! player 'inventory (lambda (old) (cons item old)))))

;; 持ち物リスとからアイテムを削除する
(define delete-item!
  (lambda (player item)
    (set-player-attr! player 'inventory (lambda (old) (delete-1 item old)))))

;; 指定したアイテムを持っているか調べる
(define has-item?
  (lambda (player item)
    (member item (get-inventory player))))

;; HPの値を取得する
(define get-hp
  (lambda (player)
    (get-player-attr player 'hp)))

;; HPの値を変更する
(define add-hp!
  (lambda (player delta)
    (set-player-attr! player 'hp (lambda (old) (+ delta old)))))

;; MPの取得と変更はget-hp、add-hp!とほとんど同じなので割愛。

;; 位置を表す値を取得
(define get-position
  (lambda (player)
    (get-player-attr player 'position)))

;; 位置を表す値を変更
(define set-position!
  (lambda (player pos)
    (set-player-attr! player 'position (lambda (old) pos))))


set-position!のupdaterだけが

(lambda (x) (☆☆ @@ x))

になってないけど、その他の手続きのupdaterのように、

  • 現在の値(x)
  • 変化量(@@)

で新しい値が決まるならば、

(define set-player-attr!
  (lambda (player attr updater delta)
    (let ((p (assoc attr player)))
      (set! (cdr p) (updater delta (cdr p))))))

でも良いよなぁー。...なんて思った。
ただしupdaterは2引数の手続きに限られてしまうけど。