Clojure Notları #2

Son günlerde kendimi programlama dillerinin temeline adadım. Amacım sadece kod yazmak değil, yazdığım kodu daha yazarken kafamda çalıştırabilmek. Bunun yanında bir programın gerçekte neler yaptığı veya yapabildiği konularınada yöneldim. Clojure ile girdiğim fonksiyonel programlama dünyasına, satın aldığım SICP(Structure and Interpretation of Computer Programs) isimli kitapla devam ediyorum. Bu kitap adından anlaşıldığı üzere programların yapısı üzerine yoğunlaşıyor. Ama bugüne kadar hiçbir kitapta görmediğim derecede güzel ve fantastik bir biçimde olaylara yaklaşıyor. MIT'de de ders olarak okutulan bu kitabı bana göre, bilgisayar bilimi ile uğraşan herkesin bitirmesi gerekiyor.

Yalnız kitabın öyle çokda kolay olmadığını söyleyebilirim. Sorular üzerinde düşünmeniz kod yazmanızdan çok uzun sürüyor. Ama genede daha birinci bölümü bitirmeden birşeylerin kafanızda canlandığını hissediyorsunuz. Eğer sizde bu dediklerimden sonra merak ettiyseniz buradan kitaba online olarak ulaşabilir veya buradan videolu anlatımlarına ve ders notlarına ulaşabilirsiniz. Hatta burada yer alan online derslere de katılabilirsiniz.

Diyeceksiniz bu kitabın Clojure ile ne alakası var. Aslında çok alakası var. Kitapta konular bir Lisp lehçesi olan Scheme kullanılarak anlatılıyor. Zaten herhangi bir Lisp diline aşinalığınız varsa kodlar size uzak gelmiyor. Ben de bu kitapta yer alan kodları hem Scheme hem de Clojure ile yazarak ilerliyorum. Hatta sırf benim bu yaptığım için üzerinde uğraşılan Sicp In Clojure kitabı bile var. Clojure yanında herhangi bir fonksiyonel dil ile de (Scala vb) bu kitabı çözebilirsiniz. Emin olun çok faydasını göreceksiniz.

Clojure'a nasıl çalıştığımı anlattıktan sonra biraz da ilk gün öğrendiklerimden bahsetmek istiyorum. Yazı içerisinde geçen teknik kelimelerin Türkçelerini yazmak isterdim ancak bazı kelimeleri çevirince gerçekten çok saçma olduğunu gördüm. O yüzden nasıl biliniyorsa aynen yazıyorum. Hem bu sayede yabancı kaynaklarda uygun bir şekilde arama yapabilirsiniz. Türkçe kaynak konusuna değinmiyorum bile.

Clojure

Clojure ile yazdığınız kodlar aşağıda gördüğünüz biçimde JVM üzerinde çalışacak hale getiriliyor. Burada diğer JVM üzerinde çalışan dillerin aksine Reader isimli bir yapının olduğunu görüyorsunuz. Bu Lispten gelen bir miras diye düşünüyorum. Reader makroları diye tamamen farklı bir konu bile var.

Clojure

Syntax

Clojure diğer Lisp ailesi dillerde de olduğu gibi parantezleri ve prefix notasyonunu kullanıyor. Diğer dillerde yazılan f(a, b, c) tarzı ifadeler, (f a b c) şekline dönüşüyor. Burada f yerine bir makro, özel operator veya fonksiyon gelmek zorundadır. a b c ise bu fonksiyona veya diğer yapılara argüman olarak gidecekler.

Comments

Clojure'da yorum satırları ; ile başlar. Eğer blok olarak yorum yapmak istiyorsanız (comment ...) şeklinde bir yapı kullanabilirsiniz.

Forms

Clojure'da Symbols, Literals, Lists, Vectors, Maps and Sets formları vardır.

Symbols: Clojure içinde isimlendirme yapmak için kullanılır. Fonksiyon isimleri(map, str, println) hep bir semboldür. Dilde operatör olmadığı için +,-,/ hepsi birer fonksiyondur.

Literals: Her dilde olduğu gibi Clojure'da da bildiğimiz sabitler var. Bunlar aşağıdaki gibi değerlendiriliyor.

 42              ; Long
 6.022e23        ; Double
 0x7F            ; Hexadecimal number(127)
 0177            ; Octal number(127)
 42N             ; BigInt
 1.0M            ; BigDecimal
 22/7            ; Ratio
 "selam"         ; String
 \e              ; Character
 true  false     ; Booleans
 nil             ; null
 +  Ahmet *cem*  ; Symbols
 :elma  :armut   ; Keywords

Burada dikkat etmemiz gereken bir detay var. Diğer dillerde yer alan / operatörü ile iki sayının bölümünden kalan tam kısmı alıyorduk. Clojure'da ise bu bölümden sonra size tam sayıyı değil, rasyonel sayıyı veriyor(pay ve payda cinsinden). Eğer birbirine bölümünü bulmak istiyorsanız bir tanesini gerçek sayı yapmanız gerekiyor. 22/7.0 gibi.

Dilde fark ettiğim ve bana çok büyük kolaylık sağlayan birşeyi söyleyim. Bazen büyük çaplı sayılar ile işlemler yapıyorum. Diğer dillerde yaparken hep zorlanmışımdır. Ancak Clojure içerisinde sayının sonuna N eklemek ile bu sayıyı BigInt olarak tanımlıyorsunuz. Ve bu sayı şimdilik işimi görecek kadar büyük sayıları tutabiliyor. Üstüne ihtiyaç duyduğumda başka olaylarıda incelerim tabi ama şimdilik gayet memnunum.

Special Forms

Özel formlar dilde en çok kullandığımız kısımdır diyebiliriz. Diğer birçok dilde de benzer olayları bulmanız mümkün. Clojure içerisinde o kadar çok bu şekilde form bulabilirsiniz ki hepsini ezberlemenize gerek yoktur. En aşağıda anlattığım gibi diğer formlara ulaşabilirsiniz.

Birden çok değeri string olarak birleştirmek istiyorsanız str formu bu iş için ideal.

 user=> (str "Hello," " world!")
 "Hello, world!"
 user=> (str 7)
 "7"
 user=> (str "Deger: " 7)
 "Deger: 7"

Diğer tüm dillerde gördüğümüz if koşulu için Clojure içerisinde de if formu var. Kullanımı gayet benzer.

 user=> (if (even? 23) "23 cift" "23 tek")
 "23 tek"
 user=> (if true "yes")
 "yes"
 user=> (if false "yes")
 nil
 user=> (if false "yes" "no")
 "no"
 user=> (if nil "yes" "no")
 "no"
 user=> (if "" true)
 true
 user=> (if 0 true)
 true
 user=> (if true "yes" "no")
 "yes"
 user=> (if (= 1 1) "yes" "no")
 "yes"
 user=> (if (= 1 1) (+ 2 4) (+ 5 5))
 6
 user=> (if (= 1 2) (+ 2 3) (+ 5 7))
 12

C'de gördüğümüz switch-case yapısının bir benzeri de clojure'da cond formudur. Bana sorarsanız daha gelişmiş ve işlevsel diyebilirim.

 user=> (def x 10)
 #'user/x
 user=> (cond (< x 0) (println "Negatif")
              (= x 0) (println "Sifir")
              :else   (println "Pozitif"))

Sadece bir durumu kontrol etmek istersek when formunu kullanabiliriz.

 user=> (when nil "buranin bir onemi yok yan taraf nil")
 nil
 user=> (when false "hala bi onemi yok durum false")
 nil
 user=> (when true "simdi geliyom")
 "simdi geliyom"
 user=> (when true 1)
 1
 user=> (when true 1 2 3)
 3
 user=> (when true
   (println "Hello, world")
   "Yes")
 Hello, world
 "Yes"
 user=> (when (= 5 (inc 4))
   "Yes")
 "Yes"

Tam tersi lazımsa when-not formu da var.

 user=> (when-not true "ilk bu" "sonra bu" "en son bu")
 nil
 user=> (when-not false "ilk bu" "sonra bu" "en son bu")
 "en son bu"

Yapılacak işlemleri belirtmek için do formu kullanılabilir.

 user=> (do (println "Selamlar") (println "Tekrardan Selam") (/ 22 7))
 Selamlar
 Tekrardan Selam
 22/7

Geçici olarak değişken tanımlamaları için let kullanılıyor. Aynı hadi x şimdi 5, y şimdi 6 olsun der gibi.

 user=> (let [x 2] (+ x 8))
 10
 user=> (let [x 2 y 8] (+ x y))
 10
 user=> (let [x 2 y 8] (= (+ x y) 10))
 true
 user=> (let [x 3] (+ (* x x) 1))
 10
 user=> (let [renk "Yesil"] (str "Renk: " color))
 "Renk: Yesil"

Döngü kurmak için loop, recur, dorun, doseq, for gibi formlar kullanılıyor. Bunları şimdilik öylesine yazıyorum. Tamamen başlı başına bir olay olduğu için ilerde detaylıca anlatmak istiyorum.

 user=> (loop [i 0]
   (when (< i 3)
     (println i)
     (recur (inc i))))
 0
 1
 2
 nil
 user=> (dorun (for [i (range 0 3)]
          (println i)))
 0
 1
 2
 nil
 user=> (doseq [i (range 0 3)]
   (println i))
 0
 1
 2
 nil

Define

Sembollere değer atamak için def formu kullanılır. Fonksiyon döndüren fonksiyon olan fn ile de dönen fonksiyona bir isim verebilirsiniz.

 user=> (def x 7)
 #'user/x
 user=> x
 7
 user=> (+ 5 7)
 12
 user=> (def listem '(1 2 3))
 #'user/listem
 user=> listem
 (1 2 3)
 user=> (last listem)
 3
 user=> (first listem)
 1

Collections

Aslında bunları bir önceki kısımda da yazabilirdim ama Clojure içerisinde de bu şekilde tanımlanıyorlar. Ve dilde gördüğüm en güzel şeylerden biriside bu yapıların birbiri arasında kolaylıkla dönüştürülebilmesi. Burada dikkat etmemiz gereken bir nokta var. Bu yapılar üzerinde yaptığımız şeyler kalıcı olmuyor. Bunun bir zorluk değil büyük bir kolaylık olduğunu zamanla anlıyorsunuz zate n.

List: Listeler diğer Lisp dillerinde olduğu gibi Clojure'da da önemli bir yere sahip. İstediğimiz herşeyi liste içerisine koyabiliyoruz. Listelerin ilk elemanla rını değerlendirmemesi için başlarına ' koyabiliriz veya (quote ) kalıbı içerisine alabiliriz.

 (4 :elma 3.0)          ; Liste
 (class '(4 :elma 3.0)) ; clojure.lang.PersistentList

Vector: Vektörler diğer dillerdeki dizilere benziyor. Liste yapısınada çok benziyor. Ancak Liste içerisindeki elemanlara index numaraları ile ulaşabiliriz.

 [1 2 3 4 5 6]                     ; Vektor
 (class [1 2 3 4 5 6])             ; clojure.lang.PersistentVector
 (["c" "l" "o" "j" "u" "r" "e"] 3) ; "j"

Set: Python'dan alışkın olduğum bu yapıda gerçekten çok faydalı. Kümeler sadece benzersiz elemanları bünyesinde barındırır. Tekrarlanan elemanlar yoksayılır.

 #{:a :b :c}                              ; #{:a :b :c}
 (class #{:a :b :c })                     ; clojure.lang.PersistentHashSet

 (hash-set "l" "i" "s" "p" "s" "p" s)     ; #{"l" "i" "s" "p"}
 (sorted-set "a" "b" "f" "c")             ; #{"a" "b" "c" "f"}

Map: Nasıl çevrildiğini bilmediğim ama kullanımına aşina olduğum bir yapı. Yukarıda gösterdiğimi keywords yapısı genellikle burada çok fazla kullanılıyor.

 (class { })                                       ; clojure.lang.PersistentArrayMap

 {:istanbul 34 :antalya 7 :trabzon 61}             ; {:istanbul 34 :antalya 7 :trabzon 61} 
 (:istanbul {:istanbul 34 :antalya 7 :trabzon 61}) ; 34

 (keys {:istanbul 34 :antalya 7 :trabzon 61})      ; (:istanbul :trabzon :antalya)
 (vals {:istanbul 34 :antalya 7 :trabzon 61})      ; (34 61 7)

Reader Macros

Lispi Lisp yapan en önemli konulardan biriside kesinlikle makroları. Bazen yapılmış öyle şeyler görüyorum ki yani adam sanki hiç normal kod yazmamış sadece işini makroyla halletmiş diyebilirsiniz. Aşağıda en çok kullanılan makroların birkaçı var. Ben şimdilik bunlarla yetiniyorum. Ama çok yakın bir sürede ciddi ciddi makrolara giriş yapacağım. Makrolar öyle birkaç satırla anlatılcak basitlikte değil zaten. Alt tarafta ilk gördüğünüz kısım makroların yazım şekli ve yan tarafta da dönüştürülmüş hali var.

 'makro     (quote makro)
 ~makro      (unquote makro)

 #'makro        (var makro)
 @makro     (deref makro)

 makro#      (gensym makro?)
 #(+ % 7)   (fn [x] (+ x 7))

Environment

doc fonksiyonu ile, hatırlayamadığınız fonksiyonun çalışmasına bakabilirsiniz.(Kod içerisinde yazılmış yorum satıları)

 user=> (doc if)
 -------------------------
 if
   (if test then else?)
 Special Form
   Evaluates test. If not the singular values nil or false,
   evaluates and yields then, otherwise, evaluates and yields else. If
   else is not supplied it defaults to nil.

   Please see http://clojure.org/special_forms#if
 nil

Eğer aklınıza fonksiyonun ismi gelmediyse find-doc ile konu ile ilgili hepsini bulabilirsiniz.(İçerisinde geçen herşeyi getirir)

 user=> (find-doc "time")
 -------------------------
 clojure.pprint/print-length-loop
 ([bindings & body])
 Macro
 ...

Benzer birşeyler arıyorsanız apropos imdadınıza yetişir.

 user=> (apropos "cat")
 (lazy-cat replicate concat mapcat)

Kaynak kodunu merak ettiklerinize source ile bakabilirsiniz.

 user=> (source iterate)
 (defn iterate
   "Returns a lazy sequence of x, (f x), (f (f x)) etc. f must be free of side-effects"
   {:added "1.0"
    :static true}
   [f x] (cons x (lazy-seq (iterate f (f x)))))

Modüller içerisindeki tüm fonksiyonlara bakmak için dir kullanabilirsiniz. Python'daki gibi.

 user=> (dir clojure.repl)
 apropos
 demunge
 dir
 dir-fn
 doc
 find-doc
 pst
 root-cause
 set-break-handler!
 source
 source-fn
 stack-element-str
 thread-stopper

Son

İlk gün burada anlattıklarımın yanında fonksiyonlarıda öğrenmiştim. Bir sonraki günde aklımda tam olarak oturmuştum. Daha sonrada fonksiyonel programlama konusunda kendimi geliştirmeye başlamıştım. Eğer bir aksilik olmazsa bundan sonraki yazımda da fonksiyonlara ve fonksiyonel programlama konusundan bahsedeceğim.

comments powered by Disqus