Clojure dilini anlamaya başlamak için öncelikle basit veri türlerini anlamaya başlamak gerek. Daha önce Clojure ile JVM ve Lisp dünyasına ‘Merhaba Dünya’ yazımda Leiningen isimli bir uygulamadan da bahsetmiştim. Şimdi bu yazımda leiningen yardımıyla Clojure ile JVM ve Lisp dünyalarına dalışa devam edeceğiz. Eğer o yazımı okumadıysanız buraya tıklayarak ulaşabilirsiniz.

Clojure, bir LISP lehçesi olarak çekirdeği küçük tutulup makrolar ile genişletilmesi düşünülmüş bir dil olmasına rağmen, JVM tabanlı bir dil ve bu nedenle Java kütüphanelerine de erişimi var. Clojure öğrenmek için veri yapılarına ve makrolar gibi başka ileri düzey konulara ilerlemeden önce mutlaka öğrenilmesi gereken konu basit veri türleri ya da başka bir deyişle değişken türleridir.

Makelem boyunca bütün anlatımlarımı REPL üzerinden yapacağım. Leiningen ile REPL çalıştırmak için. lein repl komutunu kullanabilirsiniz. Ayrıca tab ile otomatik tamamlama özelliği sayesinde ilgili kütüphaneki diğer fonksiyonları da görebilirsiniz.

Clojure terminolojisinde değişkenlere atomlar da denilebiliyor. Clojure değişken tanımlarında birçok dile göre daha özgür.Türkçe karakterler dahil bir çok alfanumerik karakteri değişken isimlerinde kullanma izni veriyor. Örnek: read-string, is-saved? ve hatta yağmur-yağıyor-mu? Clojure dilinde geçerli tanımlamalardır.

Clojure basit veri türlerini temel olarak aşağıdakiler gibi gruplandırabiliriz:

  1. Boolean (true, false)
  2. String
  3. Sayısal Veriler
  4. Düzenli ifadeler (Regular Expressions)
  5. Anahtar Kelimeler (Keywords)
  6. Fonksiyonlar

Başlamadan Önce

  • REPL’in açılımı Read, Eval, Print ve Loop dur. Bizim durumumuzda parantezler arasında kodu REPL’e gireriz ve program read yani okuma işlemini gerçekleştirir, sonra arkaplanda eval eder yani kodu çalıştırır. Örneklerde ; => ile başlayan satırlar print yani çıktıyı verir. Son olarak loop yani döngü işlemi ile başa döner ve yeni bir satırda, sizden okumak üzere yeni komut bekler. Kısacası REPL olayı bundan ibarettir.
  • REPL ekranı bazen çok kirlenip okunması zor hale gelebiliyor. Clojure, Ruby ya da Python herhangi bir dilde REPL ile çalışıyorsanız Ctrl-L kısayolu sayesinde kolayca ekranı temizleyebilirsiniz.
  • Basit veri türlerinden bahsetmeden önce Clojure’da değişken nasıl tanımlanır onu bilmekte fayda var. Clojure ile değişken tanımlamak için kullanılan şablon en basit haliyle (def <değişken adı> <değişken değeri>) şeklindedir.

##1. Boolean (true, false) Boolean, gerçekten çok basit bir veri türüdür. Aynı zamanda bilgisayar biliminin en önemli veri türlerinden birisidir. İki değer alabilir: true ya da false.

Clojure’da veri türlerini öğrenmek için kullanılan fonksiyon type fonksiyonudur.

(type true)
; => java.lang.Boolean
(type false)
; => java.lang.Boolean

type ile boolean değerlerini incelediğimizde doğrudan Java’dan geldiğini görebilirsiniz. Java zaten mükemmel çalışan boolean türüne sahipken, Amerika’yı yeniden keşfetmemek adına, JVM üzerinde çalışan bir dilin yapacağı en mantıklı hareket bence de budur.

Boolean türü doğal olarak her dilde olduğu gibi, Clojure’da da if ifadelerinin gözdesi:

Örnek:

(def yağmur-yağıyor? false)
(if yağmur-yağıyor?
	(println "Şemsiye al")
		(println "Şemsiyeye gerek yok"))
; => Şemsiyeye gerek yok

If koşulları yazarken bilinmesi gereken bazı hususlar var. Farklı diller farklı değerleri true ya da false kabul edebiliyor.

Clojure’da if içinde false olarak çalışan atomlar sadece false ve nil ‘dir. Birçok dilin aksine ”“(boş string), ()(boş liste) ve 0(sıfır) true olarak kabul edilir.

##2. String

Clojure’da string veri türünün arkasında Java’nın UTF-16 stringleri yer alıyor ve durum pek çok dille uyumlu çalışması anlamına geliyor. JVM’den aldığı güçle Java’nın string genişletme metodlarını kullanabileceğiniz gibi fazla geniş olmasa da Clojure da kendi string kütüphanesine sahip.

(type "Merhaba Dünya!")
; => java.lang.String

String ile yapılabilecek basit işlemler

Clojure ile Java’nın string fonksiyonlarını kullanabileceğiniz gibi Clojure bu işler için görece küçük bir kütüphane de barındırıyor.

Boş string testi

blank? fonksiyonunun arkasındaki soru işareti “?” bize biraz Ruby’yi hatırlatıyor. :)

(clojure.string/blank? "")
; => true
(clojure.string/blank? "Foo")
; => false

Tabiki her seferinde clojure.string yazmak uzun olacağı için alias tanımlamamız daha doğru bir yaklaşım olacaktır.

(require '[clojure.string as: str])

Bu durumda yukarıdaki örneği aşağıdaki gibi tekrar yazabiliriz.

(str/blank? "")
; => true

Büyük Küçük Harf Çevirme

REPL Üzerinde deneylerimize devam edersek:

Büyük harfe çevirme:

(str/upper-case "küçük harfli yazı")
; => "KÜÇÜK HARFLI YAZI"

Baş harfi büyütme:

(str/capitalize "küçük harfli yazı")
; => "Küçük harfli yazı"

Küçük harfe çevirme:

(str/lower-case "BÜYÜK HARFLİ YAZI")
; => "büyük harfli̇ yazi"

String Kırpma

String veri türü ile ilgili olmazsa olmaz işlemlerden biri trim yani kırpmadır. Birçok yerde yazılardan yeni satır(\n), tab(\t) ve white space dediğimiz boşlukları temizlememiz gerekir.

(str/trim "\tMerhaba trim   \n")
; => "Merhaba trim"

trim ile string verilerinizin iki tarafınıda kırpabilirsiniz. Sadece sağ ve sol taraflarını kırpmak için trimr ve triml fonksiyonları kullanılır. Kullanış şekilleri normal trim metodu ile aynıdır.

String’i kısmen değiştirme

Başka programla dillerinden de bildiğimiz replace fonksiyonu. String içinde eşleşen parçaları bizim istediğimiz başka bir string ile değiştirir.

(str/replace "Merhaba world" "world" "dünya")
; => "Merhaba dünya"

Gördüğünüz gibi replace fonksiyonu üç parametre alıyor. Yukarıdaki kod “Merhaba world” stringinde “world” gördüğü yeri “dünya” ile değiştirir. Dikkat edilmesi gereken nokta birinci parametrede geçen “world” gördüğü her eşleşmeyi “dünya” olarak değiştirecektir. Sadece ilk gördüğü noktayı değiştirmesini istersek:

(str/replace-first "world world world" "world" "dünya")
; => "dünya world world"

şeklinde kullanabiliriz.

Clojure string API’sinde bulunan tüm string metodlarını burada yazmayacağım. Diğerlerini görmek için http://clojure.github.io/clojure/clojure.string-api.html adresini ziyaret edebilirsiniz.

Java String Interop

Clojure JVM tabanlı bir dil olduğu için Java kütüphanelerine doğal erişimi olduğunu söylemiştim. Clojure stringlerinin Java string türü olduğunu ise makalenin en başında type fonksiyonu ile doğrulamıştık. Aşağıdaki örneklerle Java’nın string API’sinde bulunan instance metodlarına nasıl erişildiğini görebilirsiniz.

(.indexOf "Clojure" "j")
; => 3
(.length "Clojure")
; => 7
(.matches "Clojure" "clojure")
; => false

Örnekler bu şekilde çoğaltılabilir. java.lang.String API dokümantasyonu için buraya ve Clojure üzerinden Java kütüphanelerine erişim ile ilgili kaynak için buraya tıklayabilirsiniz.

##3. Sayısal Veriler Clojure sayıları ifade etmek için pek çok sayısal veri türü barındırır. Basitçe tam sayılar, ondalıklı sayılar ve kesirler) için farklı farklı veri türleri vardır. (int, long, double, BigInteger, BigDouble, BigDecimal, Ratio)

Bu veri türlerinin belleği nasıl kullandığı, en küçük ve en büyük değerlerinin neler olduğunu dokümantasyona bakarak öğrenebilirsiniz.

Burada özellikle üstüne durmak istediğim veri türü Ratio yani rasyonel sayı ve kesir olarak tabir ettiğimiz sayı türünü ifade etmek için kullanılır.

(type 3/4)
; => clojure.lang.Ratio

type fonksiyonu ile veri türünü tespit etmeye çalıştığımızda veri türünün Ratio olduğunu ve Clojure’ın kendi kütüphanelerinde tanımlandığını görüyoruz.

(type 0.45)
java.lang.Double

Ondalıklı bir sayı ile deney yaptığımızda ise Java’nın double sınıfına bağlı. Ondalıklı sayıları ifade eden double tipindeki değerleri kesir değerlerine dönüştürmek ise gerçekten çok kolay.

(rationalize 0.45)
; => 9/20

Uygulamalara kullanıcıdan gelen girdiler çoğunlukla string türünde olur ve bunlarla matematiksel işlem yapabilmek için sayısal türlere çevirmemiz gerekir.

(Double/parseDouble "-0.45")
; => -0.45
(Int/parseInt "45")
; => 45

Sayısal verilerden bahsetmeye başlamışken, Math kütüphanesindeki statik fonksiyonlardan bahsetmeden olmaz.

(type Math)
; => java.lang.Class
(Math/pow 2 4)
; => 16.0
(Math/PI)
; => 3.141592653589793
(Math/round Math/PI)
; => 3

##4. Düzenli İfadeler (Regular Expressions) Clojure düzenli ifade tanımlamak string tanımlamaya çok benziyor. String tanımının önüne # eklemeniz yeterli. #"[a-Z]"

Regex veri türünün hangi kütüphanede tanımlı olduğunu öğrenmek için:

(type #"[a-z]")
; => java.util.regex.Pattern

Clojure’da regex işlemlerini yürütebileceğiniz “re-“ ön eki ile başlayan toplam 6 fonksiyon bulunur: re-find, re-groups, re-matcher, re-matches, re-pattern ve re-seq

(re-seq #"[0-9]" "abc1d2ef3g")
; => ("1" "2" "3")

6 fonksiyonun tümü için açıklamaları burada bulabilirsiniz.

##5. Anahtar Kelimeler (Keywords) Anahtar kelimeler deyince ilk anda insanın aklına birçok programlama dilindeki token ifadeler geliyor. Yani while, for, class gibi. Ama Clojure dilinde anahtar kelime yani keyword deyince akla ilk gelen iki nokta üstüste(:) ile başlayan değerler. Bunları daha çok Ruby dilindeki sembollere benzetebiliriz. Örneğin :foo, :bar.

(type :foo)
; => clojure.lang.Keyword

Başka bir yazıda anlatmayı planladığım “Clojure’da Veri Yapıları” konusunda göstereceğim map yapısında bolca kullanılırlar. Keyword kullanmanın en önemli avantajlarından birisi de if ile yapılan eşitlik testlerinde çok büyük hız avantajı sağlamalarıdır.

Keyword veri türü ile küçük deneyler:

(keyword? "foo")
; => false
(def my-keyword (keyword "foo"))
; => #'user/my-keyword
(println my-keyword)
; => :foo
(keyword? my-keyword)
; => true

##6. Fonksiyonlar

Birinci sınıf vatandaş fonksiyonlar

En güzel kısmı en sona bıraktım. Clojure bir fonksiyonel programlama dili ve bu da demek oluyor ki bu dilde fonksiyonlar birinci sınıf vatandaşlar. Bu tabiri ilk kim kullanmış bilmiyorum ama gerçekten cuk oturuyor. Aslında buraya kadar pek çok hazır fonksiyon kullanarak geldik. Örn: re-seq, println aslında hep birer fonksiyondu.

Çekirdek Fonksiyonları

Şu ana type fonksiyonunu hep başka tanımlamalara karşı kullandık. Şimdi silahı kendisine çevirelim.

(type type)
; => clojure.core$type

Çıktıyı not edelim ve irdelemeye devam edelim.

Bir çok programlama dilini öğrenirken bize topla(), cikar() gibi temel fonksiyonların operatör denilen özel ifadelerle nasıl uygulanabileceği öğretilir. Şimdi bu durum Clojure’da nasılmış ona bakalım.

(+ 2 3 5)
; => 10

Yazım şekli bir yerlerden tanıdık geldi mi? Hadi bu “+” neymiş ona da bakalım?

(type +)
; => clojure.core$_PLUS_

clojure.core$type ve clojure.core$_PLUS_ hmm bir yerlere geliyoruz sanırım.

Kullanıcı Tanımlı Fonksiyonlar

Kullanıcı tanımlı fonksiyonlar anonim ya da isimlendirilmiş olabilirler. Bir Lisp lehçesi olarak Clojure, fonksiyon tanımlayabilmek için öntanımlı makrolar kullanır. Makroların çağırılması ise diğer fonksiyonlar(+, type, vs) ve özel formlar(if, def) ile aynıdır. İlk parantezden sonra anahtar kelime gelir. Böyle yazınca karışık oldu biliyorum.

Şimdi aşağıdaki JavaScript örneğini inceleyelim:

function selamla (isim) {
	return "Merhaba, " + isim;
}
selamla("Turhan");

// => "Merhaba, Turhan"

Aynı kodun Clojure versiyonu aşağıdaki gibi olacaktır.

(defn selamla [isim]
	(.concat "Merhaba, " isim))
(selamla "Turhan")

; => "Merhaba, Turhan"

İsimlendirilmiş fonksiyon tanımlarını defn makrosu ile yaptık. Peki ya anonim fonksiyonları nasıl tanımlıyoruz? Aşağıdaki JavaScript örneğine göz atalım.

var selamla = function (isim) {
    return "Merhaba, " + isim;
}
selamla("Turhan");

// => "Merhaba, Turhan"

JavaScript bilmeyipte bana küfür edenler için burada ne olduğunu açıklayayım. Teknik olarak bu kod ile daha önceki JavaScript kodu aynı işi yapıyor ancak tanımlamalarından gelen bir farklılık var. Biri doğrudan isimli bir fonksiyon olarak oluşturulmuş, diğeri ise anonim bir fonksiyon ve olduğu gibi bir değişkene atanmış.

Fonksiyonel programlama dillerinde fonksiyonlar bir veri türü olarak iş görürler ve doğrudan değişkenlere atanabilir, isim vermeye bile gerek kalmadan yine bir veri türü olarak başka bir fonksiyona parametre olabilir, hatta başka bir fonksiyonun dönüş değeri de olabilirler. Bu durum Clojure için de geçerli.

(def selamla (fn [isim] 
	(.concat "Merhaba, " isim)))
(selamla "Turhan")

; => "Merhaba, Turhan"

Son örneğimizde fn Clojure dilinde anonim fonksiyon oluşturan makro. def ise değişken tanımlayan özel form ve bunların birleşimi ile anonim fonksiyonu değişkene bağlayarak kullanıcı tanımlı bir fonksiyon oluşturmuş olduk.

Basit veri türlerinin sonuna geldik. Başka bir yazımda bu basit veri türlerini bağlayan, bir araya getiren veri yapılarından bahsedeceğim. Şimdilik hoşçakalın.