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.
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.↩︎
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.↩︎