2008-11-10
メソッドコンビネーションでFizzBuzz
CLOS |
CLOSにはメソッドコンビネーションがあり、総称関数に束ねられたメソッドの適用順序を任意に変更することができます。
標準では、通常のstandard以外に9種類ありますが、とりあえず使い方は何となく分かったものの一体何に使えるんだろうというものもあります。
という訳で、何か役に立ちそうなメソッドコンビネーションの例を考えていたのですが、とりあえず、役に立たない例としてFizzBuzzに挑戦してみることにしました。
prognコンビネーションは動作は分かりやすいく適用可能なメソッドを全部適用して行くものです。
適用の順番は、特定度の高いものから適用されますが、オプションで逆順にすることもできます(これはstandard等でも同じ)。
ということで、
(1) ONEから、|ONE HUNDRED|までのクラスを作成し
(2) TWOはONEを継承、THREEは、TWOを...と順に継承するクラス群を作成し、
(3) それぞれにメソッドを付け、
(4) このままだと100が一番先に実行されるので、:most-specific-lastで優先順位を逆転する指定
という風にしてみました。
さすがに手書はきびしいのでマクロです。
ちなみにdefine-method-combinationでメソッドコンビネーションに数字を使う定義もできるようなので、次はそれに挑戦してみたいと思います。
それだと、(defmethod fizzbuzz 3 (class) ..)のようになりそうです。
;; 総称関数を作成
(defgeneric fizzbuzz (class-num)
(:method-combination progn :most-specific-last))
(loop :for i :from 1 :to 100 :do (make-fizzbuzz i))
;; 実行
(fizzbuzz (make-instance '|ONE HUNDRED|))
;>>>
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
Fizz Buzz
16
17
Fizz
19
...
;; 型を定義して型で振り分けてみる
(defun fizzp (n) (zerop (rem n 3)))
(deftype fizz () '(satisfies fizzp))
(defun buzzp (n) (zerop (rem n 5)))
(deftype buzz () '(satisfies buzzp))
(deftype fizzbuzz () '(and fizz buzz))
;; 作成用マクロ
(defmacro make-fizzbuzz (n)
`(eval
`(progn
(defclass
,#1=(intern (format nil "~:@(~R~)" ,n))
,(if (zerop (1- ,n)) () `(,(intern (format nil "~:@(~R~)" (1- ,n)))))
() )
(defmethod fizzbuzz progn ((cls ,#1#))
(format T ,@(typecase ,n
(fizzbuzz (list "Fizz Buzz~%"))
(buzz (list "Buzz~%"))
(fizz (list "Fizz~%"))
(otherwise (list "~A~%" ,n))))))))