1.3.1 引数としての手続き

文責:@iriya_ufo

以下の3つの手続きを考える.

;; a から b までの整数の和
(define (sum-integers a b)
  (if (> a b)
      0
      (+ a (sum-integers (+ a 1) b))))

;; 与えられた範囲の整数の三乗の和
(define (sum-cubes a b)
  (if (> a b)
      0
      (+ (cube a) (sum-cubes (+ a 1) b))))

;; 級数の項の並びの和
(define (pi-sum a b)
  (if (> a b)
      0
      (+ (/ 1.0 (* a (+ a 2))) (pi-sum (+ a 4) b))))

上記の級数は次のようなものである.

\(\frac{1}{1*3} + \frac{1}{5*7} + \frac{1}{9*11} + ...\)

以上3つの手続きは共通の基本パターンを持っているのでひな形を作る事ができる.

(define (<name> a b)
  (if (> a b)
      0
      (+ (<term> a)
         (<name> (<next> a) b))))

このような共通のパターンが見つかった場合, 有用な抽象化があることを意味する. プログラムの設計者として, 特定の和を表現する手続きではなく, 総和自身の概念を表現する手続きが書きたい. 以下のように書くことができる.

(define (sum term a next b)
  (if (> a b)
      0
      (+ (term a)
         (sum term (next a) next b))))

sum は引数として下限 a と上限 b を取り, 手続き term と next を取る.

これを使って最初に提示した3つの手続きを, それぞれ抽象化することができる.

;; a から b までの整数の和
(define (identity x) x)

(define (sum-integers a b)
  (sum identity a inc b))

(sum-integers 1 10) ;; => 55

;; 与えられた範囲の整数の三乗の和
(define (inc n) (+ n 1))

(define (sum-cubes a b)
  (sum cube a inc b))

(sum-cubes 1 10) ;; => 3025

;; 級数の項の並びの和
(define (pi-sum a b)
  (define (pi-term x)
    (/ 1.0 (* x (+ x 2))))
  (define (pi-next x)
    (+ x 4))
  (sum pi-term a pi-next b))

(* 8 (pi-sum 1 1000)) ;; => 3.139592655589783

抽象化された sum ができると, 更なる概念を作るための部品とすることができる.

例えば (a, b) 間の関数 f の定積分は数値的には式

\[\int_{a}^{b} f = \left[ f \left( a + \frac{dx}{2} \right) + f \left( a + dx + \frac{dx}{2} \right) + f \left( a + 2dx + \frac{dx}{2} \right) + ... \right] dx\]

と表される. これを手続きとして表現できる.

(define (integral f a b dx)
  (define (add-dx x) (+ x dx))
  (* (sum f (+ a (/ dx 2.0)) add-dx b)
     dx))

(integral cube 0 1 0.01) ;; => .24998750000000042
(integral cube 0 1 0.001) ;; => .249999875000001

Previous topic

1.3 高階手続きによる抽象

Next topic

1.3.2 lambdaを使う手続きの構築