by shigemk2

当面は技術的なことしか書かない

リスト遊び 6-2 補助関数

前回
リスト遊び 6-1 二項演算 - by shigemk2

前回こんなのを書いたけど、

(defun calc (exp)
  (cond
   ((atom exp) exp)
   ((eq (car (cdr exp)) '+)
    (+ (calc (car exp))
       (calc (car (cdr (cdr exp))))))
   ((eq (car (cdr exp)) '-)
    (- (calc (car exp))
       (calc (car (cdr (cdr exp))))))
   ((eq (car (cdr exp)) '*)
    (* (calc (car exp))
       (calc (car (cdr (cdr exp))))))))
calc
(calc '(1 + (2 * 3)))
7

car だの cdr だのが多くとても読みにくい。
入力データの構造を知らないと理解できないコードになってしまっている。
というわけで、補助関数を導入する。
二項演算式から二項演算子、左の二項演算式、右の二項演算式を取り出す関数を
それぞれop、arg1、arg2とする。この3つの補助関数を使って、calcを書き直す。

(defun op (exp) (car (cdr exp)))
op
(defun arg1 (exp) (car exp))
arg1
(defun arg2 (exp) (car (cdr (cdr exp))))
arg2

(defun calc (exp)
  (cond
   ((atom exp) exp)
   ((eq (op exp) '+)
    (+ (calc (arg1 exp)) (calc (arg2 exp))))
   ((eq (op exp) '-)
    (- (calc (arg1 exp)) (calc (arg2 exp))))
   ((eq (op exp) '*)
    (* (calc (arg1 exp)) (calc (arg2 exp))))))
calc
(calc '(1 + (2 * 3)))
7

上記のように、プログラムの中の具体的な手続きを補助関数として
抽象化することで、全体の見通しがよくなる。

補助関数の第一の利点は、適切な補助関数名をつけておけば、関数の定義を
読まなくても、どのように動作するか想像がつくことである。
operator は op
argument の省略形 arg1、arg2とした。

補助関数の第二の利点は、本体の関数を入力データの構造から独立させられることである。
本体の関数では、入力データの構造を意識せずに、本質的な機能を実現することに専念できる。
また、入力データの構造の変更を迫られた場合、補助関数を変更するだけでよく、本体の関数を修正する
必要がなくなる。

補助関数の利点

  • 関数名から動作を推測できる(もちろん動作を的確に表すような名前を選ぶ必要はある)
  • 補助関数により入力データの構造を隠蔽できる。入力データの構造を変更する際には、補助関数のみを修正すれば変更が必要なところすべてに反映される。

リスト遊び―Emacsで学ぶLispの世界 (ASCII SOFTWARE SCIENCE Language)

リスト遊び―Emacsで学ぶLispの世界 (ASCII SOFTWARE SCIENCE Language)