by shigemk2

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

リスト遊び 6-3 連想リスト と funcall

前回
リスト遊び 6-2 補助関数 - 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

呼び出す関数を実行時に決定できればよさそうである。
二項演算子名からその計算を実行する関数名を得て、その関数を呼び出せばよろしい。
連想リストを使って、二項演算子から関数名が連想できればよい。
というわけで、前のassqを使う。
リスト遊び 4-6 連想リスト その2 - by shigemk2

funcallについてだが、funcallの第一引数が、呼び出したい関数の名前になる。

(funcall '+ 1 2)
3
;; ドット対を返す
(defun assq (key alist)
  (cond
   ((null alist) nil)
   ;; 先頭のドット対を調べ、ドット対のCARが指すデータがkeyと同一なら、その先頭のドット対を返す
   ((eq key (car (car alist))) (car alist))
    (t (assq key (cdr alist)))))
assq

;; リストの中身を取り出す
(defun op (exp) (car (cdr exp)))
(defun arg1 (exp) (car exp))
(defun arg2 (exp) (car (cdr (cdr exp))))

;; 呼び出すための演算子が格納された連想リストop-func1を作成する
(setq op-func1 '((+ . +) (- . -) (* . *)))
((+ . +) (- . -) (* . *))

;; 連想リストから演算子を取り出す関数を作成する
(defun op-func (sym op-db)
(cdr (assq sym op-db)))
op-func
(op-func '+ op-func1)
+
(op-func '- op-func1)
-
(op-func '* op-func1)
*

;; funcallと、assqを利用して、本関数を短くする
(defun calc (exp op-db)
  (cond
   ((atom exp) exp)
   (t (funcall (op-func (op exp) op-db) ;; 演算子を取り出して、関数を呼び出す
	       (calc (arg1 exp) op-db)
	       (calc (arg2 exp) op-db)))))
calc
(calc '((3 + 4) * 2) op-func1)
14

;; 二項演算子名を自由に変更出来るという点がある。
(setq op-func2 '((add . +) (sub . -) (mul . *)))
((add . +) (sub . -) (mul . *))
;; こういう風に書いても同じ結果が得られる
(calc '((3 add 4) mul 2) op-func2)
14

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

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