osa1 feed

Common Lisp için Clojure usulü multimethodlar

January 8, 2012 - Tagged as: lisp, tr.

Bir önceki yazımda biraz bahsetmiştim Clojure ve Common Lisp multimethodları arasındaki farklardan. Bugün Common Lisp için olabilecek en basit Clojure usulü multimethod implementasyonu yaptım. 2 macro ve toplamda 14 satır sürdü. Örnek olarak Joy of Clojure kitabındaki bir kod parçasını Common Lisp ile yazacağım. Clojure hali şöyle:

(defmulti compiler :os)
(defmethod compiler ::unix [m] (get m :c-compiler))
(defmethod compiler ::osx  [m] (get m :c-compiler))

Burda yapılan şey şu, compiler adlı bir multimethod oluşturuluyor ve dispatch fonksiyonunu seçmek için kullanılacak fonksiyon olarak :os keywordu olarak belirleniyor1 Daha sonra iki tane method tanımlanıyor, ilkinde test fonksiyonumuz(yani :os fonksiyonu) :unix keywordünü dönerse çalıştırılacak fonksiyon, ikincisinde de :osx keywordünü dönerse çalıştırılacak fonksiyonu belirleniyor. Test fonksiyonuna da m parametresinin aktarıldığına dikkat. Yani m önce test fonksiyonu tarafından kullanılıyor, sonra da dönüş değerine göre dispatch fonksiyonlarından biri tarafından.

Kullanımı şöyle:

(def unix {:os ::unix, :c-compiler "cc"})
(def osx  {:os ::osx,  :c-compiler "gcc"})
(compiler unix)
=> "cc"
(compiler osx)
=> "gcc"

multimethodların test fonksiyonunu ve bunun dönüş değerlerine karşılık gelen dispatch fonksiyonlarını tutmaları lazım. dönüş değeri-dispatch fonksiyonu ikililerini bir hash-table’da tuttum. Her bir multimethod için 2 tane closure oluşturdum, bir tanesi yeni methodlar eklemek istediğimizde çağırılık dönüş değeri-dispatch fonksiyonları ikililerini tutan hash-table’ı güncelleyecek, diğeri de testi yapıp hash-table’dan fonksiyonu çekip çağıracak.

(defmacro defmulti (name (&rest args) dispatch-fn)
  (let ((dispatch-table (gensym)))
    `(let ((,dispatch-table (make-hash-table :test #'equal)))
       (defun ,name (,@args)
         (funcall (gethash (funcall ,dispatch-fn ,@args) ,dispatch-table)
                  ,@args))
       (defun ,(intern (concatenate 'string (string name) "-ADD-METHOD"))
           (dispatch-fn-return-val method)
         (setf (gethash dispatch-fn-return-val ,dispatch-table) method)))))

Görüldüğü gibi multimethodlar aslında normal fonksiyonlar(aslında closure, dispatch-tableı tutuyor). Bu sayede herhangi bir fonksiyona aktarılabilirler. Özel bir yapı yok yani ortada. Bir de aslında çaktırmadan tanımladığımız multimethod’a -add-method eki getirerek bir fonksiyon daha oluşturuyoyruz. Bunu kullanıcının çağırmasına hiç gerek yok, sadece yeni method ekleme işlemini kolaylaştırmak için.

(defmacro defmulmethod (name dispatch-fn-return-val method)
  `(,(intern (concatenate 'string (string name) "-ADD-METHOD"))
    ,dispatch-fn-return-val
    ,method))

defmethod adı Common Lisp’e ait olduğundan adını defmulmethod yaptım. Önceki macroda oluşturulan -add-method fonksiyonu yardımıyla dispatch-tablea yeni fonksiyonu ekliyor. Bundan sonra aynı örneği Common Lisp ile şöyle yapabiliriz.

(defmulti compiler (x) (lambda (x) (gethash :os x)))
(defmulmethod compiler :unix (lambda (x) (gethash :c-compiler x)))
(defmulmethod compiler :osx (lambda (x) (gethash :c-compiler x)))

(setf unix (make-hash-table))
(setf (gethash :os unix) :unix)
(setf (gethash :c-compiler unix) :cc)


(setf osx (make-hash-table))
(setf (gethash :os osx) :osx)
(setf (gethash :c-compiler osx) :gcc)
CL-USER> (compiler unix)
:CC

CL-USER> (compiler osx)
:GCC

Common Lisp halinin çok daha uzun olmasının birkaç sebebi var: Birincisi, Common Lisp hash-tablelarının başlangıç değerlerini belirlemenin bir yolu yok. hash-table’ların özel bir syntax’ı da yok. make-hash-table ile oluşturup teker teker elemanları koymamız gerekiyor. İkincisi, 2 notta yazdığım şey.



  1. Clojure hakkında sevdiğim bir özellik, keywordler aynı zamanda fonksiyon, çağırıldıklarında parametre olarak bir map alıyorlar ve anahtar görevi görerek değeri dönüyorlar.

  2. Clojure hakkında sevdiğim bir özellik, keywordler aynı zamanda fonksiyon, çağırıldıklarında parametre olarak bir map alıyorlar ve anahtar görevi görerek değeri dönüyorlar.