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"))
(method)
(dispatch-fn-return-val 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-valmethod)) ,
defmethod
adı Common Lisp’e ait olduğundan adını defmulmethod
yaptım. Önceki macroda oluşturulan -add-method
fonksiyonu yardımıyla dispatch-table
a yeni fonksiyonu ekliyor. Bundan sonra aynı örneği Common Lisp ile şöyle yapabiliriz.
lambda (x) (gethash :os x)))
(defmulti compiler (x) (lambda (x) (gethash :c-compiler x)))
(defmulmethod compiler :unix (lambda (x) (gethash :c-compiler x)))
(defmulmethod compiler :osx (
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.↩︎