8-1-2012, 1:32 ö.s. //
//
lisp
// 90
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 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.
6-1-2012, 3:11 ö.s. //
//
lisp
// 105
Tamamen Common Lisp ile basit bir IRC botu ve web arayüzünün nasıl yazılabileceğinden bahsedeceğim biraz. Web kısmında web server dahil herşey yine Common Lisp ile yazılmış olacak.

Gistler: arayüz bot
Öncelikle botun ne yapacağına karar verelim, benim amacım çalışan en minimal botu yazmak. Daha sonra üzerine istediğimiz özelliği ekleyebiliriz. Bu yüzden şimdilik sadece bağlandığı kanalları ve kendisine atılan özel mesajları kaydedip, bir web sayfasında yayınlayacak.

Öncelikle IRC sunucusuna bağlanabilmemiz için bir socket kütüphanesine ihtiyacımız var(evet Common Lisp standardı socket içermiyor). Bu iş için usocket'i seçtim(uğraşacak olan varsa bir diğer alternatif de iolib, fakat ikisi için de işe yarar dökümantasyon yok dolayısıyla her türlü yolumuzu kendimiz bulmamız gerek).
Programımızın ana döngüsü gayet basit:
(defun run ()
(let* ((socket (socket-connect "irc.freenode.org" 8001))
(socket-stream (socket-stream socket))
(start-time (get-universal-time)))
(loop
(let ((msg (read-line socket-stream)))
(format t "~A~%" msg) ;; debug ve gozetleme amacli
(multiple-value-bind (prefix command params) (parse-msg msg)
(handle-command prefix (intern (string-upcase command)) params socket-stream)))
(let ((time-passed (- (get-universal-time) start-time)))
(when (> time-passed (* 1 30))
(update-html)
(setf start-time (get-universal-time)))))))
Bir socket oluşturup Freenode sunucularına bağlanıyoruz. Socket'e yazma ve socket'den okuma işlemlerini socket'in stream'ine yapacağız[1]. read-line ile sunucudan her seferinde bir tam satır okuyoruz ve parse-msga gönderiyoruz. parse-msg gelen mesajı IRC RFC'de belirtilen mesaj formatında göre prefix, command ve params olarak 3 parçaya bölüyor ve Common Lisp'in values özel formu ile bunları dönüyor[2]. Daha sonra bu parçaları handle-command generic fonksiyonuna gönderiyoruz. handle-command command parametresine göre gerekli dispatch fonksiyonunu çağırıyor[3]. Daha basit olamazdı. Son olarak yeterli vakit geçtiyse(ben 30 saniyede bir güncelliyordum sık sık debug ile uğraştığımdan), static html sayfalarını güncelleyecek olan update-htmli çağırıyoruz. Burda zamanı çok da düzgün tutmadığımıza dikkat. Eğer socket'den 10 dakika yanıt gelmezse 30 saniyede bir de güncelliyor olsak 10 dakika beklemek zorundayız[4].
Mesajları parçalara ayıran fonksiyonumuz şöyle:
(defun parse-msg (msg)
"Parse irc message to prefix, command and params.
http://www.irchelp.org/irchelp/rfc/chapter2.html#c2_3_1
<message> ::=
[':' <prefix> <SPACE> ] <command> <params> <crlf>
<prefix> ::=
<servername> | <nick> [ '!' <user> ] [ '@' <host> ]
<command> ::=
<letter> { <letter> } | <number> <number> <number>
<params> ::=
<SPACE> [ ':' <trailing> | <middle> <params> ]
"
(let* ((first-space (position #\space msg))
(first (subseq msg 0 first-space))
(rest (subseq msg (1+ first-space)))
(prefix (if (eq (elt first 0) #\:)
(subseq first 1)
nil))
(second-space (position #\space rest)))
(if prefix
(let ((command (subseq rest 0 second-space))
(params (subseq rest (1+ second-space))))
(values prefix command params))
(let ((command (subseq first 0 first-space))
(params rest))
(values nil command params)))))
Ben burda ciddi bir şekilde parse etmektense, önek, komut ve parametreleri birbirlerinden ayıran boşluklar olduğunu farkettim ve basitçe bu boşluklara göre ayırdım. Saatlerdir log tutuyor henüz bir problem yaşamadım.
Mesajları parçaladıktan sonra şu generic fonksiyona gönderiyoruz:
(defgeneric handle-command (prefix command params socket-stream))
Bu fonksiyonu çağırırken command parametresinin her zaman bir sembol olması lazım. Başka türlü command parametresine göre dispatch fonksiyonuna karar veremiyoruz[5]. Ana döngüde string'i sembole çevirdiğim hacky kısım bu yüzden.
Şu aşamada ilgimizi çeken 3 komut var. PRIVMSG, NOTICE, ve PING. PING komutunu sunucu bize, uzun süre yanıt vermediğimiz için gönderecek(sürekli dinlemede olacağımızdan) ve hemen PONG cevabını vermemiz lazım. PRIVMSG herhangi bir kanala veya bize bir mesaj gönderildiğinde gelecek. NOTICEde ne zaman login olmak için komut göndermemiz gerektiğine karar vermemiz için. Burda en kritik olanı PRIVMSG, diğerlerine gist'den bakabilirsiniz:
(defmethod handle-command (prefix (command (eql 'privmsg)) params socket-stream)
(let ((channel-or-nick (subseq params 0 (position #\space params)))
(sender (subseq prefix 0 (position #\! prefix)))
(msg (subseq params (+ 2 (position #\space params)))))
(multiple-value-bind (channel-message-queue channel-exists)
(gethash channel-or-nick *channels*)
(unless channel-exists
(setf (gethash channel-or-nick *channels*) '()))
(setf (gethash channel-or-nick *channels*)
(cons (make-message :msg msg
:sender sender) channel-message-queue)))))
*channels*, tüm kanallar için bir liste tuttuğumuz hash tablomuz. Burda static web sayfalarını güncelleme vaktimiz gelene kadar gelen mesajları tutuyoruz(gelen mesajı yine IRC RFC'nin şu bölümüne göre parçalıyorum). Mesajları tuttuğumuz yapımız basitçe sadece mesajın içeriğini ve göndereni tutuyor:
(defstruct message msg sender)
Son olarak static sayfaları güncellemek için çağırdığımız update-html:
(defun update-html ()
(loop for channel-or-nick being the hash-keys of *channels* do
(let ((msgs (reverse (gethash channel-or-nick *channels*))))
(with-open-file (file-stream (concat "/home/sinan/Desktop/cl/logs/"
(if (equal #\# (elt channel-or-nick 0))
(subseq channel-or-nick 1)
"direct-messages")
".html") ;; remove # from channel name
:direction :output
:if-exists :append
:if-does-not-exist :create)
(with-html-output (file-stream)
(dolist (msg msgs)
(let ((message-text (concat (message-sender msg)
"> "
(message-msg msg))))
(htm (:p :class "msg" (str message-text)))))))
(setf (gethash channel-or-nick *channels*) '()))))
Burda html çıktısını üretmek için cl-who kütüphanesini kullanıyoruz[6]. with-open-file ile kanal adına ait dosyayı açıp(yoksa oluşturup, varsa sonuna ekleyerek) with-html-output ile html elementlerini Lisp formları ve keywordler ile yazarak html kodunu dosyaya yazıyoruz ve hash tablomuzdaki mesaj listesini boşaltıyoruz(henüz tüm sayfayı oluşturmuyoruz, sadece mesajları html formatında kaydettik).
Şu anda sunucuda istediğiniz kanalları dinleyip kaydeden bir botumuz var(kod hakkında eksik olan birkaç tanımlama için en başta verdiğim gistlere bakabilirsiniz).
İkinci adım olarak web arayüzü. Static sayfaları sunmak için Hunchentoot kullanacağız. Bu gibi basit işler için inanılmaz rahat bir kütüphane.
(defvar server (make-instance 'easy-acceptor :port 4242
:document-root "/home/sinan/Desktop/cl/static"))
(start server)
Hunchentoot ile 4242. portu dinleyen bir sunucu oluşturduk ve başlattık. document-root, static dosyaların(css dosyaları, resimler vs.) tutulduğu klasör. Hunchentoot sayfa yönlendirmelerini *dispatch-table* listesinden yapıyor. Yönlendirme işlemi birkaç farklı dispatcher ile yapılabiliyor ama biz şu anki basit sayfamız için sadece kanal adlarını yönlendirmekle ilgileneceğiz. Bu yüzden kullanacağımız dispatcher prefix-dispatcher olacak.
(defmacro define-url-fn ((name) &body body)
`(progn
(defun ,name ()
,@body)
(push (create-prefix-dispatcher ,(format nil "/~(~a~).html" name) ',name) *dispatch-table*)))
Genel olarak sayfa oluşturma yapımız bu. (define-url-fn (sayfa-adi) icerik) şeklinde çağırdığımızda, localhost:4242/sayfa-adi adresinde iceriki gösterek şekilde ayarlıyor. Bu kadar basit. Şimdi sayfa içeriğimizi oluşturmadan önce her sayfada olacak kısımları ayıralım:
(defmacro standard-page ((&key title) &body body)
`(with-html-output-to-string (*standard-output* nil :prologue t :indent t)
(:html
(:meta :charset "utf-8")
(:head
(:title ,title)
(:link :type "text/css"
:rel "stylesheet"
:href "/static/reset.css")
(:link :type "text/css"
:rel "stylesheet"
:href "/static/main.css"))
(:body
(:div :class "main"
,@body)))))
Bu şekilde sadece bir sayfayı diğer sayfadan ayıracak kısımlarla ilgileneceğiz ve standard-page macrosuna göndereceğiz. Web arayüzümüz tamamen ayrı bir program olduğundan, önce logların tutulduğu klasöre bakıp kanal listesini çıkaralım:
(defun list-file-names (&optional (folder *log-folder*))
(mapcar (lambda (pathname)
(let ((filename (file-namestring pathname)))
(pathname-name filename)))
(directory (make-pathname :directory folder :name :wild :type "html"))))
Burda yapılan bariz gibi. Bir klasördeki html dosyalarının adlarını listeliyoruz. Bu kadar. Bu aşamadan sonra ana menü(kanal listesinin bulunduğu) ve kanal loglarının görüntüleneceği sayfaları oluşturmak kalıyor.
;; ana menu
(define-url-fn (log-list)
(standard-page (:title "log list")
(:div :class "header" "Channel List:")
(loop for log in (list-file-names)
collect (htm (:div :class "menulink"
(:a :href (concat log ".html") (str log)))))))
Kanal log sayfalarını oluşturmak biraz zor oldu ve aslında yukarıda hazırladığım hiçbir macroyu kullanmadım. Benim gibi herhangi bir Lisp diline yeni başlayanlara bir not(gerçi Scheme macroları epey farklıymış, pattern matching yapabiliyorlarmış ve hijyeniklermiş): Bir macroya parametre olarak macro alan bir macro gönderiyorsanız ve macrolara tam olarak hakim değilseniz, debug etmek yerine elle yazmak daha pratik olabilir ehehe:
;; kanal loglari
(dolist (page-name (list-file-names))
(let* ((in (open (merge-pathnames *log-folder*
(make-pathname :name page-name
:type "html"))))
(text (car (loop for line = (read-line in nil)
while line collect line))))
(push
(create-prefix-dispatcher
(concat "/" page-name ".html")
(lambda ()
(standard-page (:title "Channel logs")
(:div :class "header" (str (concat "Chat logs for #" page-name)))
(str text))))
*dispatch-table*)))
Ve bu kadar. Bot + web arayüzü toplamda 207 satır. Yorumlar dahil.
Eklenebilecekler:
- Common Lisp HyperSpec Lookup: Kendisine atılan bir mesajla CLHS'den istenilen sembolle alakalı döküman linkini getirebilir.
- Link başlıkları: Kanala atılan linklere girip başlığı kanala yazabilir.
- Çok sorulan sorular için cevaplar: Yetki verilen bazı kullanıcılar belirli komutlar için yazılacak cevabı belirleyebilir. Birisi bir soru sorduğunda bota ona istenilen cevabı vermesi söylenebilir.
- Hata/durum(exception/condition) kontrolü. Bağlantı bir şekilde kesildiğinde yeniden bağlanabilir vs.
[1]: Common Lisp'in bir güzel yanı, lexical scope veya direkt olarak parametre olarak aktararak, kullanıcıya yazdırıp kullanıcıdan okuduğumuz fonksiyonların hepsini dosya, socket vs. için çok rahat kullanabiliyoruz
[2]: Common Lisp'de bir fonksiyon birden fazla değer dönebiliyor, epey ilginç bir özellik, gerekliliği tartışılır tabii, sonuçta bir tuple/list/vs. dönmekten pek bir farkı yok, yanlızca eğer özel olarak belirtilmezse çağırana sadece otomatik olarak ilk değer dönüyor böylece her fonkisyon için "acaba kaç değer dönüyor?" diye düşünmüyoruz.
[3]: Burada Clojure'dan bahsetmem lazım. Clojure multimethod'ları isteğe bağlı dispatch fonksiyonları ile çalışıyorken Common Lisp bu konuda daha kısıtlı. Clojure multimethodları hakkında şuraya bakabilirsiniz. Pascal usta buna benzer bir yapıyı Common Lisp için implement etmiş. Şurda biryerlerdeydi ama bulamadım şimdi.
[4]: Socket'ler konusunda çok bilgili değilim. usocket ve iolib kütüphaneleri direkt olarak unix socketlerini(posix socketleri mi oluyor?) implement etmişler, api olarak çok benzerler. Yine de ben non-blocking io yapmayı bir türlü beceremedim. Thread'lerden de bir süredir nefret ediyorum. Common Lisp'de çok kullanılan Bordeaux Threads kütüphanesinde(Common Lisp standardının thread de içermediğini söylemiş miydim?) de timer yok.
[5]: Common Lisp'in karşılaştırmayı eql fonksiyoinu ile yapmasıyla alakalı. Daha önce bahsettiğim kısıtlama.
[6]: Lisp dillerinin bir başka güzel yanı: kendi syntaxları ile kolaylıkla herhangi bir markup dilini ifade edebilirsiniz(şansınızı zorlarsanız JavaScript'i bile ifade edebilirsiniz ama bana delilik gibi geliyor açıkçası, bkz. parenscript).
31-12-2011, 10:38 ö.ö. //
//
python
, java
, lisp
// 116
Bugün çok fantastik birşey gördüm, anlatmazsam ölürüm(uygun bir başlık düşünmem tüm yazıyı yazmamdan daha uzun sürdü o yüzden idare edin hehe).
Diyelim ki bir veri yapısı tasarlıyoruz, bir nodedan bir sürü başka nodea veya elemana pointerlar olacak. Bir yandan da bellek kullanımını minimum tutmak istiyoruz. Pointerları tutan arrayimizde hiç boş yer olmamalı.
Bir bitmap tutuyoruz. Büyük ihtimalle integer oluyor(Java primitive int tipi 32bit olmak zorunda mesela). Diyelim ki bu node'un n. indexine bir eleman/pointer ekleyeceksiniz. Bitmap ilk başta 0 tabii. Şu şekilde bitmap'de ilgili elemanı 1 yapıyoruz:
Buraya kadar herşey çok basit. Bu noktadan sonra bu bitmape göre 30. elemanın arrayde nereye denk geldiğini bulmamız lazım. Bunun için şu formülü kullanıyoruz:
ctpop, population count fonksiyonu, yani bir sayının iki tabanında gösterilişindeki 1 bitleri sayıyor. Bu Java'da Integer.bitCount fonksiyonu(öhm, static methodu) ile bulunabilir.
Birkaç deneme yaparak nasıl yaptığını anlayabilir ve kendimizi ikna edebiliriz:
In [2]: bmp = 0
In [3]: bmp |= 1 << 15
In [4]: ctpop(bmp & ((1<<15)-1))
Out[4]: 0
In [5]: bmp |= 1 << 21
In [6]: bmp |= 1 << 10
In [7]: ctpop(bmp & ((1<<10)-1))
Out[7]: 0
In [8]: ctpop(bmp & ((1<<15)-1))
Out[8]: 1
In [9]: ctpop(bmp & ((1<<21)-1))
Out[9]: 2
Eğer arraydeki n. yere bir eleman ekliyorsak, n'den itibaren arrayi bir kaydırmamız lazım. En büyük index her zaman arrayde de daha sonda oluyor.
Ne yaptığına bakalım. 25. elemanın arraydeki yerine bakarken, 1 << 25i hesaplıyorum ki bu aslında (2 tabanında) 1 ve yanına 25 tane 0 koymak demek. Daha sonra bu sayıdan 1 çıkararak, en sağdan itibaren(en anlamsız bitten itibaren) tüm 0ları 1 yapıyorum, ilk gördüğüm 1'i 0 yapıyorum, gerisine dokunmuyorum(bu şartlar altında geriye kalan tüm bitler 0 oluyor). Daha sonra bu sayı ile bitmap'i logical and(bazı yerlerde bitwise and diyor, aynı şeyler sanırım?) işlemine sokup ctpop yaptığımda, bitmap'de (1 << n)'den itibaren kaç tane 1 olduğunu saymış oluyorum ve bu da bana array'deki indeximi veriyor. Çok mantıklı.
Bu arada kullandığınız dile göre bu işlemi daha kolay bir şekilde yapabilirsiniz. Bazı diller(Java'da Integer.bitcount, Common Lisp'de logcount gibi) direkt olarak bitCount gibi fonksiyonlar sunuyor. Bir de ben Common Lisp'de hiç kaydır 1 çıkart falan demeden direkt "şu bitle şu bit arasında kaç 1 olduğunu say" şeklinde bir fonksiyon yazdım, bitwise trickler yapmadan, şöyle:
(defun ctpop (bitmap &key (start 0) (end 32))
(logcount (ldb (byte (- end start) start) bitmap)))
19-12-2011, 1:25 ö.s. //
//
// 181
Aslında uzun süredir yazdığım kodları github'da tutuyor olsam da git'i son birkaç günde çok daha iyi anladığımı söyleyebilirim. Şu ana kadar yaptığım, sürekli tek bir branch üzerinden çalışarak, diğer bilgisayar geçeceğim zaman veya çalışan bir sonraki sürümü yayınlamak istediğimde push yapmaktı. Pardus'da geliştirdiğimiz web projesinde bile sadece stajın son gününde, tek bir commit yaptık hehe(neredeyse tüm projeyi tek bir bilgisayarda pair-programming ile geliştirdiğimizden ihtiyaç duymamıştık, değişiklikleri göstermek istediğimde makinamda kurulu apache'yi çalıştırıyordum ve ağ üzerinden test ediyorlardı. tüm dosya sistemim zaten ağ üzerinden paylaşıma açıktı).
Son günlerde büyük oranda deneyse bir 2d platform oyunu üzerinde çalışıyorum ve sık sık eski haline dönmeyi isteyebileceğim ciddi değişiklikler yapıyorum. Git bana bu konuda nasıl yardımcı olabilir diye biraz araştırdım ve şu ana kadar bunları nasıl farketmedim diye kızdım kendime :P .
Kendime not niteliğinde, bazı olmazsa olmaz(benim için) komutları açıklayacağım.
Başlangıç, depo oluşturma veya mevcut depoyu ekleme
Anladığım kadarıyla bir depoya yazma izninin belirlenmesi biraz da sunucu tarafıyla alakalı. Bizim yapmamız gereken git config --global user.name "isim" ve git config --global user.email "email" ile commitlerimizde gözükecek ismi ve maili belirlemek(mail nerede gözüküyor bilmiyorum). Daha sonra eğer github kullanıyorsak ssh anahtarı oluşturup github'a ekliyoruz. Github'da anladığım kadarıyla ssh key'den kullanıcı adına bakıp depoya yazıp yazamayacağımıza karar veriyor. Bu kısımdan emin değilim açıkçası ama aklıma benim neden Clojure github deposuna push yapamadığımı açıklayacak başka bir senaryo gelmedi aehueah.
Git sunucunuzda depoyu bir şekilde oluşturduktan sonra(Github'da web sayfasından tıklayarak falan) git init ile bilgisayarımızda git deposu oluşturup, buna uzak sunucudaki depoyu git remote add ile ekliyoruz:
➜ ~ mkdir git-test
➜ ~ cd git-test
➜ git-test git init
Initialized empty Git repository in /home/sinan/git-test/.git/
➜ git-test git:(master) git remote add git-test git@github.com:osa1/git-tests.git
(Bu arada not: zsh kullanıyorum, oh-my-zshda süper eklentiler var, örneğin benim kullandığım git eklentisinde yukarıda da görebileceğiniz gibi bulunduğum branch'ı gösteriyor, commitlenmemiş değişiklikler varsa bir sembolle belli ediyor, git <tab> yaptığınızda komutları, komutlardan sonra <tab> yaptığınızda branch adlarını vs. tamamlıyor, mükemmel birşey, bir sürü de tema var ayrıca.)
Depoyu oluşturduktan sonra hiç branch'ımız yok aslında. Ama eğer branch oluşturmadan direkt commit yaparsak master diye bir branch oluşturuyor. master adının bir esprisi yok anladığım kadarıyla. Direkt olarak başka isimli bir branch da oluşturulabilir.
git-status ile commitlenmemiş değişiklik olup olmadığını görebiliyoruz. git-log tüm branchlara yapılan tüm commitleri bir 53ea667012c2741e7620e093e05132cd08265c06 benzeri kodla gösteriyor. Bu kodun tam olarak ne olduğunu anlamasam da, önceki bir commit'e dönmem gerektiğinde kullanıyorum.
Branchlar ile alakalı işlemler
➜ git-test git:(master) git checkout -b "branch2"
Switched to a new branch 'branch2'
➜ git-test git:(branch2) git branch
* branch2
master
checkout normalde zaten olan bir brancha geçer. Ama eğer "yeni oluştur ve hemen geç" diyorsanız, -b ile bu şekilde kullanılıyor. git branch ile de hangi branchda olduğunuzu görürsünüz(benim yaptığım gibi bir zsh eklentisi kullanıyorsanız zaten gözüküyor gerçi).
Branchlar hakkında yazmak istediğim birkaç şey var. Bir tanesi hangi branchda olursanız olun, başka bir brancha merge yapmadan da o brancha push yapabilirsiniz. Şu şekilde:
➜ git-test git:(branch2) git push git-test branch2:master
Bu syntax'a dikkat: branch2'yi master'a pushla diyorum. Gerekli merge'leri kendisi yapıyor, yapamıyorsa push da yapmıyor. Tabii sonra master branch'ına geçip fetch(master) + merge veya direkt merge(branch2'den) yapmak lazım.
Bunu kullanarak şöyle bir çakallık yapabiliyorsunuz, diyelim ki sunucudan bir branchı uçurmak istiyorsunuz:
➜ git-test git:(branch2) git push git-test :branch2
Bu aslında "branch2'ye hiçbir şeyi pushla" gibi bir anlama geliyor(ben uydurdum aehuaeh). branch2'yi sunucudan siliyor yani. Bilgisayarınızdaki depodan da şu şekilde siliyoruz:
➜ git-test git:(master) git branch -D branch2
Ha neden bir branch'ı sunucudan silmek isteyesiniz bilmiyorum. Geri dönmek isteyebilirsiniz belki, sunucuda durmasında bir zarar yok gibi.
Push ve pull
Şimdi, yukarıda bir branchdan başka bir brancha push yapılabildiğinden bahsetmiştim. Ama pratikte bu bir kolaylık sağlamıyor(sonuçta diğer brancha geçtiğinizde yine fetch + merge(veya sadece merge) yapmanız lazım.
pull ve fetch + merge'den bahsetmek lazım. fetchin yaptığı aslında sunucunuzdaki depodaki istediğiniz branchı depo-adi/branch-adi adlı bir brancha çekmek. Daha sonra bunu branch-adi ile merge etmek istiyorsanız git merge depo-adi/branch-adi ile merge ediyorsunuz. İşte git pull depo-adi branch-adi tam olarak bunu yapıyor.
➜ git-test git:(master) git fetch git-test master
From github.com:osa1/git-tests
* branch master -> FETCH_HEAD
➜ git-test git:(master) git merge git-test/master
Auto-merging test.markdown
CONFLICT (content): Merge conflict in test.markdown
Automatic merge failed; fix conflicts and then commit the result.
İşte şu anda yaptığım git pullun yapacağının aynısı.
Yanlız burda anlamadığım bir nokta, eğer bu depo-adi/branch-adi branchına geçersek şöyle bir mesaj alıyoruz:
➜ git-test git:(master) git checkout git-test/master
Note: checking out 'git-test/master'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:
git checkout -b new_branch_name
HEAD is now at 53ea667... Merge remote branch 'git-test/master'
Ne demeye çalıştığını çözemedim.
Son olarak hayatımda gördüğüm en güzel şey(abarttım), eski commite geri dönme:
➜ git-test git:(branch3) git checkout 692832aa7a4a3a092e35f6955676be3c6e54e89e -b branch4
Switched to a new branch 'branch4'
Burdaki commit kodundan ilk başta bahsetmiştim. Yeni bir branch oluşturup eski commite dönüyorum.
Commit hakkında birkaç şey
commit -a, daha önceden takipe aldığınız(git add ile) dosyalardan değişiklik olanları otomatik olarak commite ekler. Teker teker yeniden git add yapmanıza gerek kalmaz(ben ilk başta -a tüm dosyaları ekliyor sanıyordum mesela). commit -m, commit mesajı için $EDITOR çevre değişkenindeki editorü çalıştırmak yerine(bu kısım sanırım sadece bash için geçerli) komut satırından mesaj girmenizi sağlar. Ben commitlerimi şöyle yapıyorum yani:
➜ git-test git:(master) git commit -a -m "mesaj"
Stash
Bu özellik yeni başlayanlar için çok kritik değil sanırım, ama bir arkadaşla bir proje üzerinde çalışırken(ne yaptığımız hakkında hiçbir fikrimiz yok tabi aehueah) git bize "git stash yap istersen" gibi uyarılar veriyordu. Biz de anlam veremiyorduk.
stashın olayı şuymuş, diyelim ki bir branchda değişiklikler yaptınız, bir bug gözünüze çarptı düzelteceksiniz, sadece o kısım düzelmiş olarak commit yapacaksınız. Hemen git stash save ile o ana kadar yaptığınız commitlenmemiş değişiklikleri kaydediyorsunuz ve hemen son commitli haline dönüyor branch. Bugfix yapıp commit/pushluyorsunuz, sonra git stash pop ile son kaydettiğiniz stashe dönüyorsunuz. Birden fazla stash olabiliyor, git stash list ile listeliyorsunuz. İşiniz bittiyse git stash drop ile stashı siliyorsunuz falan. Henüz kullanma fırsatım olmadı, okuduklarımı yazıyorum. Kullandığımda örneklerle düzenleyebilirim.
Biraz dağınık oldu sanırım, amacım burda git'e veya sürüm kontrolüne yeni başlayanlara öğretmek değil de(o kadar bilmiyorum zaten), benim gibi bazı şeylerin ne kadar kolay/kullanışlı olduğunun farkında olmayanlara birşeyler göstermek ve kendime not düşmekt, o yüzden idare edin :P .
30-11-2011, 11:11 ö.s. //
//
lisp
// 173
~2 ay kadar önce okumaya başladığım SICP hakkında birkaç birşey yazmak istedim. Okumak isteyenlere de birkaç tavsiyede bulunacağım. Bu kitabın yanlış anlaşıldığını, küçümsendiğini düşünüyorum. SICP nedir, veya neden SICP diye düşünen varsa, Google'da küçük bir araştırma sizi süper sayfalara yönlendirecektir.
Öncelikle biraz ön bilgi vereyim, kitabı ciddi anlamda okumaya ~2 ay kadar önce başladım, ve fonksiyonel programalama, Lisp dilleri, ve haliyle genel olarak programlama hakkında epey tecrübem vardı. Kitabı ilk okuma denemem lisans'a başladığım dönem(hatta hafta) içinde, bir şekilde biryerlerden kitapdan haberdar olup kütüphanemde bulunduğunu öğrenmemle oldu. Birkaç ay sonra da şöyle bir feed girmişim. Farkedebileceğiniz gibi pek de iyi gitmemiş. Şu anda ise ikinci ve üçüncü bölümdeki 179 alıştırmadan 168'ini çözdüm ve not aldım(%93.8). Çözmediklerimin 5 tanesi 2. bölümün son 5 sorusu, artık konuyu anladığımı düşündüğümden çözmeyeceğim, geriye kalan 6 tanesini de çözemedim, aklımdalar.
İlk bölüm hariç çözdüğüm tüm alıştırmaların çözümleri ve açıklamaları github alanımda. Kitabı okumaya başladığımda çözümlerimi yayınlama gibi bir amacım yoktu, o yüzden ilk bölümdeki alıştırmaların çözümlerini not almadım. Bu aşamadan sonra ~2 hafta kadar ara verip(biraz finallerle ilgileneyim de şu dönemi kazasız belasız atlatıyorum diyorum), kitabı bitirmeye devam edeceğim. Gerçi ara verebileceğimi sanmıyorum, yine okuyacağım ama alıştırmalarla şu ana kadarki gibi yoğun bir şekilde ilgilenemeyeceğim.
Şimdi biraz okumak isteyenler için birşeyler söyleyeyim:
Çalışma ortamı
Emacs'e aşina değilseniz veya benim gibi VIM olmadan düz metin bile yazamayacak halde değilseniz, Racket en ideal ortam gibi. Kendi REPL/Editor'ü yeterince iyi. Kurulumu çok kolay(hatta linux ortamında kurulum yapmadan da çalıştırabiliyorsunuz). "Picture language" bölümündeki gibi tanımı verilmemiş fonksiyonlar kolayca temin edilebiliyor(yeri geldiğimde açıklamalarımda yazdım bunları).
Racket'in kötü yanı, tam olarak Scheme olmadığından, bazı kısımlar çalışmıyor, farklı bir yol izlemeniz gerekiyor. Örneğin mutable data ile ilgilendiğimiz 3. bölümde. Racket'ın pairları immutable olduğundan(Scheme'dekinin aksine), set-car! ve set-cdr! gibi fonksiyonlar yok. Çözümü, bir modül aracılığıyla pair yerine mpair(mutable pair), set-car! yerine set-mcar! vs. kullanmak. Bunları hep açıklamalarda yazdım yeri geldiğinde.
Bu kısımlar için de ben kullandığım dağıtımın(openSUSE 11.4) paket yöneticisinde gördüğüm Scheme48'i kullandım. Her hangi bir standart Scheme implementasyonu kullanılabilir.
Emacs'e alışkın olanlar, veya illa VIM tuşları diyenler için çözüm Quack. Açıkçası profesyonel anlamda Scheme yazacaksanız ne kadar iyidir bilemiyorum, benim ihtiyacım olan Scheme REPL'i ve editorden kulayca kod çalıştırmayı mümkün kılıyor. Tuşları SLIME ile epey benzer. İstediğiniz Scheme'i kullanabiliyorsunuz(run-schemei çalıştırdığınızda kullanmak istediğiniz Scheme'i soruyor). Eğer Racket kullanıyorsanız, mzschemei çalıştırıyorsunuz. Emacs VIM tuşları için de Evil.
Bu arada kitabın bir sürü ders videoları var sağda solda. Berkeley ve MIT'ninkiler gayet iyiydi diye hatırlıyorum. Bana süper sıkıcı geldiğinden(gözlerim kapanıyor ya) dersleri izlemedim.
Zorluk
Baştan söyleyeyim, kitap zor. Alıştırmaları çözmeden öğrenmeniz imkansız. Örneğin ikinci bölümde 90 küsür alıştırma var. Konunun bir kısmı(hatta belki de çoğu kısmı) alıştırmalarda anlatılıp, program yazarak öğretiliyor. Alıştırmalar birbirlerine bağlı, çoğu zaman bir bölüm içinde önceki alıştırmaları çözmeden sonrakini çözemiyorsunuz. Bazı bölümlerde bir paragraf birşey anlatıp, birkaç paragraflık problemler geliyor falan. Çoğu alıştırma çok zevkli olsa da, bazen kendini tekrar edebiliyor(örneğin 2. bölümün sonlarına doğru).
İlk 3 bölümü epey hızlı bitirdiğimi düşünüyorum. Bu şu yüzden olabildi: Kitaba başladığımda, fonksiyonel programlama, Lisp dilleri, ve haliyle programlama hakkında epey tecrübeliydim. Bir süre Common Lisp ile uğraşmıştım, The Little Schemer'ı okumuştum, ve Clojure ile uğraşıyordum. Özellikle ilk bölüm için çok gerekli olan bazı matematiksel kavramları ayrık matematik ve calculus derslerinde görmüştüm. Kitabı ilk okuma denememde aslında bu yüzden yapamamıştım. İşin matematiğinde takılmıştım, ve ilk bölüm matematik ağırlıklı(Scheme biliyorsanız bile kesinlikle atlamamalısınız, iterative process vs. recursive process, tail-call optimization, basit lambda calculus, fixed-point combinator gibi süper konulardan bahsediliyor). Bunların hepsine sıfırda başlayacak biri için bu süreç epey uzun olabilir(MIT veya bu kitabın okutulduğu diğer okullarda kaç dönem sürüyor tüm kitap acaba?).
Fonksiyonel programlama
3. bölüme kadar tamamen fonksiyonel programlama yapıyoruz. 3. bölümde de atama işleminden, mutable datadan, avantajlarından, dezavantajlarından, programları nasıl kompleks bir hale getirdiğinden, fonksiyonel/imperative programlama arasında dengeden bahsediliyor. Daha sonra %100 fonksiyonel bir yol izlemesek de(3. bölümü yeni bitirdim, tam emin değilim) geri kalan kısım yine büyük oranda fonksiyonel gibi.
Burda SICP'in fonksiyonel programlama için mükemmel bir kaynak olduğunu düşünüyorum. Sadece ilk 3 bölüm okunsa bile yeterli olur. 3. bölüme geçtiğimde hiç farkında olmadan sürekli fonksiyonel programlama yaptığımı ve artık bu yolun bana çok doğal geldiğini farkettim. Bundan 1 yıl kadar önce imperative dillerde çok rahat çözebildiğim tüm problemleri şu anda fonksiyonel bir şekilde çözebiliyorum(hatta belkide daha bile rahatımdır, bir süredir imperative dillerle uğraşmıyorum). SICP bu şekilde düşünmeye başlayabilmek için mükemmel bence.
Common Lisp vs Scheme vs Clojure
Bazen denk geliyorum, SICP'i Common Lisp veya Clojure ile çözmeye çalışıyor birileri. Bunun mantıklı olduğunu düşünmüyorum. SICP'i bir Scheme kitabı olarak görmeyin, dil hakkında birşler anlattığı kısımları birleştirsek, ilk 3 bölümde toplam 10 sayfa bile etmez. Scheme çok küçük bir dil. Pek çok dili öğrenirken vaktimizin çoğunu syntax'ını anlamakla geçiriyoruz zaten(programlamaya yeni başlamıyorsak veya çok farklı bir paradigm öğrenmiyorsak). Scheme'de öyle bir dert yok. Ekstra bir dil öğrenmiyorsunuz yani aslında.
Kaldı ki Scheme(ve aslında Common Lisp, ama kendisi çok kompleks bir dil olduğundan bu süreç epey uzun sürecektir) sadece kendisi ile yazılan kitaplar için bile öğrenilebilir bence.
Bir de olaya şu açıdan bakın, bir programlama kitabı düşünün, anlattığı konuların herhangi bir dille alakası yok, genel olarak programalama, programların yapımı, soyutlama, problemlere yaklaşımlar, concurrency, programların yorumlanması ile alakalı, bir yerden sonra okuyucuya o ana kadar kullandığı dilin yorumlayıcısı yazdırılıyor. Bir programlama dili kitabı değil kesinlikle. Bir derleyici kitabı da değil. Daha iyi bir dil biliyor musunuz?
SICP hakkında şimdilik bu kadar. Paylaşmak istediğim birşeyler olursa yine yazacağım. Üzerinde çalıştığım(henüz elle tutulur birşey değil ama) bir Scheme yorumlayıcısıyla alakalı da dağınık notlar var, bir ara ekleyeceğim(4. bölüm tam olarak bu işle alakalı olduğundan, sanırım 4. bölümü bitireceğim önce).
0 ,
1 ,
2 ,
3 ,
4 ,
5 ,
6 ,
7 ,
8 ,
9 ,
10 ,
11 ,
12