Common Lisp için Clojure usulü multimethodlar
// yorumlar // lispBir ö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 belirleniyor[1]. 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, [1]. 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.
blog comments powered by Disqus

