Clojure'da Basit Veri Türleri
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:
- Boolean (true, false)
- String
- Sayısal Veriler
- Düzenli ifadeler (Regular Expressions)
- Anahtar Kelimeler (Keywords)
- 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 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:
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.
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. :)
Tabiki her seferinde clojure.string yazmak uzun olacağı için alias
tanımlamamız daha doğru bir yaklaşım olacaktır.
Bu durumda yukarıdaki örneği aşağıdaki gibi tekrar yazabiliriz.
Büyük Küçük Harf Çevirme
REPL Üzerinde deneylerimize devam edersek:
Büyük harfe çevirme:
Baş harfi büyütme:
Küçük harfe çevirme:
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.
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.
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:
ş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.
Ö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 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.
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.
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.
Sayısal verilerden bahsetmeye başlamışken, Math kütüphanesindeki statik fonksiyonlardan bahsetmeden olmaz.
##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:
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
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
.
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:
##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.
Çı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.
Yazım şekli bir yerlerden tanıdık geldi mi? Hadi bu “+” neymiş ona da bakalım?
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:
Aynı kodun Clojure versiyonu aşağıdaki gibi olacaktır.
İ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.
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.
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.