2011/02/17

ちゅーしょーか

手続きの抽象化はどこまでやるべきか迷うことがあります。手続きに限らずクラスなんかもそうです。考えうる最高の抽象度で手続きを定義して具体的な名前のラッパ手続きを定義するようなことは日常的にありますが、結局最も抽象度の低い手続きしか使わないこともしばしば。引数の数やシグネチャによってディスパッチするようにしたりキーワード引数やオプショナル引数を用意、けど実際には使うことがなかった、なんてこともよくあります。早過ぎる最適化ってやつですかね。

ということは、思いつくまま書いて、必要になってから初めて抽象度の高い関数を定義し、それを元の関数から呼ぶようにリファクタリングするのが正解なのかなあ。でも、思いついたまま書いたら書いたで、規模や内容によっては後から分離したり抽象度を上げたりするのが難し(辛)くて、結局「最初から抽象度を高く定義しときゃよかったわ・・・」となり放置あるいわ断念ということもしばしば。
プログラミング自体やロジックを考えたりするのも簡単ではありませんが、こういう落とし所の判断も難しいれす。「現時点ではこれで良い。」と判断するのってなかなか勇気がいります。それとも判断しようとすること自体が余計なのか。必要になるまで判断しない、とか。lazy に。


例としては小粒過ぎるかもしれませんが、例えば
三つの数を引数としてとり、大きい二つの数の二乗の和を返す手続きを定義せよ。
(SICP の 1.3 の問題ですね)

準備。
(define (square x)
  (* x x))

現実の開発でも大きな問題を細切れにしていくとこのくらいの粒度の問題に行きつきますよね。抽象度はばらけてますが、最後の例は抽象度が高いと思います。でもここまでやるか、ということです。その手前くらいで良いような気がします。

問題に対してバカ正直に
;; cond
(define (sum-of-square-picked-big2 a b c)
  (cond ((and (< a b) (< a c))(+ (square b) (square c)))
        ((and (< b a) (< b c))(+ (square c) (square a)))
        (else (+ (square a)(square b)))))

(sum-of-square-picked-big2 1 2 3)
;; -> 13
(sum-of-square-picked-big2 9 10 8)
;; -> 181

ソートして先頭の2つを使う
;; sort
(define (sum-of-square-picked-big2 . args)
  (let1 sls (sort args >)
    (+ (square (car sls))
       (square (cadr sls)))))

(sum-of-square-picked-big2 1 2 3)
;; -> 13
(sum-of-square-picked-big2 1 2 3 4 5 6 7 8 9 10)
;; -> 181

先頭から指定した分を取得して計算するケース
;; head
(define (head n ls :key (sorter identity))
  (let rec ((n n)(ls (sorter ls))(acc '()))
    (if (zero? n)
        acc
        (rec (- n 1)(cdr ls)(cons (car ls) acc)))))

(define (pick-big n nums)
  (head n nums :sorter (lambda (ls)
                         (sort ls >))))

(define (sum-of-square-picked-big2 . args)
  (apply + (map square (pick-big 2 args))))


(sum-of-square-picked-big2 1 2 3)
;; -> 13
(sum-of-square-picked-big2 9 10 8)
;; -> 181

「選択」して「計算」するとしたケース
(use gauche.experimental.app)
(define (pick-calc picker calculator . args)
  ($ calculator $ picker args))

(define (sum-of-square-picked-big2 . args)
  (apply pick-calc
         (^l (pick-big 2 l))
         (^l (apply + (map square l)))
         args))

(sum-of-square-picked-big2 1 2 3)
;; -> 13
(sum-of-square-picked-big2 1 2 3 4 5 6 7 8 9 10)
;; -> 181

ちょっと例が微妙過ぎたか。

リファクタリング―プログラムの体質改善テクニック (Object Technology Series)

0 件のコメント:

コメントを投稿