Scala’da Koşul İfadeleri

Herkese merhaba!

Bu yazımda programlamanın en temel konularından olan koşul ifadelerinden bahsedeceğim. Anlatacaklarım daha çok bu ifadelerin Scala’daki işleyişi ve diğer dillerden, özellikle Java’dan farkları hakkında olacak.

Haydi başlayalım.

if Özel Kelimesi

if özel kelimesi neredeyse bütün programlama dillerinde var olan, İngilizcede eğer anlamına gelen bir kelime. Yaptığı şey de kodunuzun belli koşul(lar)a göre farklı davranmasını sağlamak. Scala’daki söz dizimi de Java’daki ile birebir aynı. Basitçe birkaç örnek ile nasıl göründüklerine bakalım.


if (1 == 1 && (true || false) {
// Bu her zaman çalışacak
val mesaj = "1 == 1"
println(mesaj)
} else if (2 != 2) {
// Bu hiç çalışmayacak
println("2 != 2")
} else {
// Bu hiç da çalışmayacak
println("1 != 1")
}
// Gövdede tek bir ifade olduğunda süslü parantez kullanmayabilirsiniz.
if (1 == 1 && true) println("1 == 1") else println("1 != 1")

Gördüğünüz gibi çok sıradışı bir şey yok. Süslü parantez kullanımı konusunda önerim; süslü parantezleri sadece eğer 14. satırdaki ifade kadar basit bir ifade yazacaksanız kullanmayın ve ifadenizi bu şekilde tek satırda yazın. Bunun dışındaki durumlarda if, else if ve else gövdelerinde süslü parantezler kullanın.

Şimdi Scala’daki if’in farklı bir yönüne bakalım. Scala’da koşul ifadeleri, döngü ifadeleri, süslü parantezler içine yazılmış birden fazla ifadeden oluşan bloklar gibi birçok yapı aslında yorumlanır ve bir değer üretir. Yani if için konuşacak olursak, bir if-else if-else topluluğunun bütünün ürettiği bir değer vardır. Bunun Java ile karşılaştırmalı bir örnek ile daha kolay anlaşılacağını düşünüyorum.


// Java
String metin = "merhaba";
int uzunlukSeviyesi; // 1, 2 veya 3
if (metin.length < 2) {
uzunlukSeviyesi = 1;
} else if (metin.length < 4) {
uzunlukSeviyesi = 2;
} else {
uzunlukSeviyesi = 3;
}
// Scala
val metin = "merhaba"
val uzunlukSeviyesi = if (metin.length < 2) {
1
} else if (metin.length < 4) {
2
} else {
3
}

Örnekte görebileceğiniz gibi, if ile başlayıp else bloğunun kapanan süslü parantezi ile biten kod parçasının bütünü aslında bir değer üretiyor. Bu durumda tür çıkarımı ile Int ürettiğini görüyoruz ve bunu uzunlukSeviyesi isimli val’a atıyoruz. Eğer if, else if ve else bloklarının gövdelerinin hepsi aynı türden bir değer üretmeseydi (örneğin else içinde 3 yerine “3” olsaydı), bu sefer tüm bloğun üreteceği değerin türü Int ve String’in ortak en yakın üst türü olan Any olacaktı. Kontrol ifadesi bu şekilde bir değer üretebildiği için, üretilen değer ile normalde herhangi bir değerle yapabildiğiniz tüm işlemleri yapabilirsiniz. Örnekte sonucu bir val’a atadık ama bunu bir metodun dönüş değeri olarak da belirleyebilir, başka bir metoda parametre olarak gönderebilir, kısacası bir değer ile yapabileceğiniz her şeyi yapabilirdik. 🙂

match … case İfadeleri

Scala’daki match … case ifadeleri, Java’daki ve başka bazı dillerdeki switch … case ifadelerine karşılık gelir ama onlardan çok daha fazla yeteneğe sahiptir. Hatta bir makalede “switch-case’in steroidli (daha güçlü) hali” şeklinde bir tanıma rastlamıştım. 🙂 match … case ifadeleri bir değeri farklı birçok durum ile karşılaştırır ve eşleşen case’in gövdesini çalıştırır. Tıpkı if gibi, match … case de değer üretir. Şimdi bir önceki örnekten devam ederek neler yapabildiğini görelim.
Java


String metin = "merhaba";
int uzunlukSeviyesi = metin.length < 2 ? 1 : (metin.length < 4 ? 2 : 3);
switch (uzunlukSeviyesi) {
case 1:
System.out.println("Çok kısa");
break;
case 2:
System.out.println("Kısa");
break;
default:
System.out.println("Çok kısa");
}

Scala


val metin = "merhaba"
val uzunlukSeviyesi = if (metin.length < 2) 1 else if (metin.length < 4) 2 else 3
uzunlukSeviyesi match {
case 1 => println("Çok kısa")
case 2 => println("Kısa")
// Yukarıdaki hiçbir case uygun değildi, varsayılan case
case _ => println("Uzun")
}
// Veya koşulları case ifadelerine yerleştirmek istersek if'i kullanabiliriz.
metin match {
// Sıra önemli, önce bu case çalışacak.
// Ayrıca if'ten sonra parantez kullanmak zorunda değilsiniz.
// Burada m için tür çıkarımı yapılacak, "metin" üzerinde match yapıldığı için m bir String olacak.
case m if m.length < 4 => {
println("Kısa")
}
case m: String if m.length < 2 =>
// match … case ifadelerinde süslü parantez kullanmak zorunda değilsiniz.
println("Çok kısa")
case _ => println("Uzun")
}
// Veya doğrudan metni karşılaştıralım ve cevap için değer üretelim.
val metin2 = "slm"
val cevap = metin2 match {
case "selam" => "Ve aleykümselam."
case "merhaba" => "Sana da merhaba."
// Bu bir MatchError
}
val cevap2 = metin2 match {
// Artık birden fazla durumu eşleştirebiliyoruz.
case "selam" | "slm" => "Ve aleykümselam."
case "merhaba" => "Sana da merhaba."
// Anlamadığımız için söyleneni aynen iade ediyoruz. 🙂
case _ => metin2
}

Java’daki switch … case kullanımını görüyorsunuz.  Şimdi Scala’daki haline bakalım.

2. satırdaki if ifadesinin tek satıra sadeleştirildiğini görüyorsunuz. Java’daki halini de tek satır haline getirdim ama aralarında ciddi bir anlaşılırlık farkı var. 🙂

4. satırda başlayan blok Java’daki switch … case ifadesinin birebir karşılığı ve uzunlukSeviyesi değeri üzerinde bir eşleştirme yapılıyor. Söz dizimindeki farklılıkları görüyorsunuz. match kelimesi eşleştirilecek değerden sonra geliyor, her case’ten sonra => (eşittir ve büyüktür) işareti geliyor, break kelimesi yok ve default kelimesi yerine değeri _ ile boş bırakılmış başka bir case var. Burada tıpkı Java’daki gibi case’lerin gövdelerini yazarken süslü paranteze ihtiyaç duymuyoruz. Okunabilirliği artırmak için kullanabilirsiniz de. Size kalmış. 🙂

12. satırda başlayan blokta eşleştirmeyi doğrudan metin değeri üzerinde yapıyoruz. Bu sefer yazdığımız case’lerimizde tanımlanan eşleştirmelere ek olarak koşullar da var. Eşleştirilen değeri farklı bir isimle kullanabiliyorsunuz. Bu vereceğiniz isim sadece o case’in gövdesinde tanımlı oluyor. Şu anda mantıklı gelmese de match … case ile ilgili daha gelişmiş konuları anlattığımda bunun aslında önemli bir özellik olduğunu göreceğiz. Diğer detayları koddaki yorum satırlarına yazdım o yüzden atlıyorum. Burada case’in gövdesini süslü parantezler içine yazabileceğimizi de görüyorsunuz.

28. satırda başlayan blokta türü String olan bir değeri doğrudan eşleştirmeye çalıştığımızı görüyorsunuz. Burada yazdığımız case’ler yetersiz kaldığı için eşleştirme yapılamayacak ve bir MatchError hatası alacağız.

35. satırda başlayan blokta ise yukarıdaki sorunu çözüyoruz ve match … case ifadesinden bir değer üretiyoruz. İlk case’te gördüğünüz gibi birden fazla değer ile eşleştirme yapılıyor. Bu değerler birbirinden | (dik çizgi) işareti ile ayrılıyor. Ayrıca olası bir MatchError’ı önlemek için varsayılan bir case de ekledik. Burada tüm case’lerden String türünde değerler üretildiği için match … case ifadesinin tamamının değeri, ve dolayısıyla cevap2, String türünde olacak.

Scala’daki match … case ifadeleri ile birçok şey yapılabildiğini ve Java’daki switch … case ifadelerinden daha güçlü olduğunu görüyorsunuz. Hatta yapabildikleri bunlarla da sınırlı değil. İleride ayrıca anlatacağım pattern matching denilen işlemi de match … case ifadeleri ile yazıyoruz ve bu oldukça güçlü bir araç.

Sonuç

Bu yazımda koşul ifadelerini ve bunların Scala’daki davranışlarını, diğer dillerdeki hallerine göre üstünlüklerini ve kullanışlılıklarını anlatmaya çalıştım. İnşallah faydalı olmuştur.

Görüşmek üzere. Hoşça kalın!

Scala’da Türler ve Tür Çıkarımı (Type Inference)

Herkese merhaba! Bu yazımda Scala’nın veri türlerinden ve türlerle ilgili güzel bir özelliğinden bahsedeceğim.

Haydi başlayalım.

Scala’daki Veri Türleri ve İlişkileri

Diğer birçok programlama dilini öğrenirken olduğu gibi Scala’yı öğrenirken de dilde bulunan temel veri türlerini, bunların birbirleriyle, daha karmaşık türlerle ve sizin sonradan tanımlayabileceğiniz türlerle ilişkilerini mümkün olduğunda erken öğrenmek gerekiyor. Aşağıda Scala’daki türlerle ilgili genel bir şema görüyorsunuz.

602885_762560083757382_1615763773_n

Şimdi detaylara inelim.

1. Temel Türler

Başka dillerde, özellikle Java’da, geliştirme yaptıysanız aşina olduğunuz basit veri türleridir.

Bellekte kapladığı alana göre küçükten büyüğe Byte, Short, Int, Long, Float, Double türleri sayısal değerler için; Char türü tek bir karakter saklamak için; Boolean değeri true veya false olabilen mantıksal değerler için; Unit de Java’daki void türüne karşılık gelen hiçlik için tanımlanmış türlerdir. Bu türlerin hepsi AnyVal türünden türer. Ayrıca bu türlerin hepsi derlenmiş bytecode seviyesinde doğrudan Java’daki karşılıkları olan türlerdir. Yani Scala bu türleri kullandığınızda arka tarafta doğrudan Java türlerini kullanır.

Scala bu temel türler arasında gizli dönüşümler yapabilir. Böylece dönüşüme uygun bir türü başka bir türü gerektiren yerde kullanabilirsiniz. Bu dönüşümler yukarıdaki şemada da görebileceğiniz gibi; sayı türleri için küçük olanından büyük olana dönüşümler ve Char türünden Int türüne dönüşümdür. Örneğin Double gerektiren bir yerde Float türünden bir değeri kullanabilirsiniz, dönüşüm kendiliğinden yapılacaktır.

2. Any ve Nothing

Any türü tüm türlerin üstündedir. Tüm Scala değerleri, nesneleri ve türleri Any’den türerler. (Java’daki Object’ten farklıdır, birazdan bahsedeceğim.)

Nothing türü de tüm türlerin altındadır. Nothing, tüm Scala değerlerinden, nesnelerinden ve türlerinden türer. Bu tür, özellikle birçok tür için genel (Array gibi herhangi bir türde olabilen, türü de parametre olarak alan yapılarda) kodlar yazarken, türler arasındaki ilişkileri tanımlamada işe yarayabiliyor.

3. String, Null ve ScalaObject

Scala’daki String türü özel bir tür değildir. Aynen temel türler gibi aslında doğrudan Java’daki String türünü kullanır.

Null türü Nothing türü gibidir ama her türün değil, temel türler hariç tüm türlerin altındadır. Bellekteki geçersiz bir referansı temsil eder. Null kavramı başta Java olmak üzere diğer birçok dilde sevilmeyen bir kavramdır ve Scala’da da uzak durulmasını tavsiye ederim. 🙂

ScalaObject türü Scala’da tanımlanmış, temel türler dışındaki tüm türlerin üstündedir. Bahsi geçen tüm türler ScalaObject’ten türerler. Java’daki Object’e benzer ama birebir karşılığı değildir.

4. AnyVal ve AnyRef

AnyVal türü Scala’daki temel türlerin üst türüdür. Bu tür sayesinde sadece temel türden olan değerleri karşılayacak kodlar yazmak daha kolay hale geliyor.

AnyRef türü de Java’da tüm türlerin üstünde olan Object‘e karşılık gelen türdür. ScalaObject olan her tür (temel türlerden olmayan, referans türü olan tüm Scala nesneleri) ve Scala kodu içinde erişilen Java nesneleri AnyRef’ten türerler.

Tür Çıkarımı/Tahmini (Type Inference)

Scala statik türlü bir dildir. Yani tanımladığınız her değerin daha derleme anında bir türü olması gerekir. Java ve onun gibi statik türlü başka birçok dilde bir değer, değişken, parametre vb. tanımlarken türünü de belirtmeniz gerekir. Bu durumlarda Scala’da da tür belirtebilirsiniz. Ama dilerseniz türü yazmayabilirsiniz ve Scala sizin yerinize tür hakkında çıkarım yapabilir. Türü belirtmediğinizde dinamik türlü gibi görünen, daha kısa ve öz bir koda sahip olursunuz ama hala yazdığınız kod statik türlüdür ve türle ilgili bir sorun varsa bunu derleme anında yakalarsınız. Daha önce Scala’da aslında kesinlikle gerekli olmayan şeylerin atılabildiğinden bahsetmiştim. Tür çıkarımı da buna güzel bir örnek. 🙂 Küçük bir kod parçasıyla tür çıkarımının nasıl göründüğüne bakalım.


val a: Char = 'a'
val b: Int = 3
val c: Long = 5
val d: Boolean = true
// Scala Double'ı Float'a tercih eder. Özellikle Float istediğimiz için tür dönüşümü yapıyoruz.
val e: Float = (3.14).asInstanceOf[Float]
val f: String = "e"
val any1: Array[Any] = Array(a, b, c, d, e, f)
val k = 'k' // k bir Char olacak.
val l = 5 // l bir Int olacak.
val m = 8L // m bir Long olacak.
val n = false // n bir Boolean olacak.
val o = 3.14 // o bir Double olacak. (e'deki not yüzünden)
val p = "o" // p bir String olacak.
// any2 bir Array[Any] olacak.
val any2 = Array(k, l, m, n, o, p)

Yukarıdaki örnekte tür çıkarımından yararlanarak tanımladığımız değerlerin türlerini belirtmeyebileceğimizi görüyorsunuz. Ayrıca tür dönüşümü yapmanın da bir örneği var. Hatta türler arasındaki ilişki yüzünden any1 ve any2 değerlerinin, verilen türlerin hepsinin üst türü olan Any türünden bir Array olduğunu da görüyorsunuz. Yukarıdaki şemada bulunan ama açıklamadığım List, Seq, Iterable gibi türler ve burada bahsettiğim Array türleri ile – hatta Scala’nın koleksiyon kütüphanesi ile – ilgili daha sonra detaylı bir yazı yazacağım. Scala’nın koleksiyon kütüphanesi öyle kısaca anlatılacak gibi değil çünkü. 🙂

Peki tür çıkarımı herhangi bir yerde türleri yazmaktan vazgeçebileceğiniz anlamına mı geliyor? Hayır. Aşağıdaki örneğe bakalım.


def dogru(a: String, b: Int, c: Boolean): Long = {
// Burada Long türünde bir değer olacak.
}
// Metodun dönüş değerinin türü de çıkarımla bulunabilir çünkü gövdedeki son ifadenin değeri geri döndürülecek.
// Haliyle metodun dönüş değerinin türü de bu son ifadenin değerinin türü olacak.
// Dolayısıyla metod imzasından dönüş türünü atabiliriz.
def buDaDogru(a: String, b: Int, c: Boolean) = dogru(a, b, c)
// Bu hata verecek çünkü Scala statik türlü.
// Metod a, b, ve c ile bir şeyler yapacak. Türlerinin bilinmesi gerek.
def buYanlis(a, b, c) = {
// Neredeyim ben? Neler oluyor?
}

Burada metodların dönüş türünü tür çıkarımı için yazmayabileceğinizi ve tür çıkarımını kullanmanın yaratabileceği bir sorunu görüyorsunuz. Henüz bahsetmedim ama, kendi class’larınızı ve türlerinizi tanımlarken ve buradaki gibi metod parametreleri tanımlarken – türün kesin olarak bilinmesi gereken yerlerde – tür çıkarımı kullanmaya çalışmak hata almanıza neden olacaktır.

Tür çıkarımı ilginç, güzel, kullanışlı ama aynı zamanda biraz da tehlikeli bir özellik. Muhtemelen ilk başlarda her yerde kullanmak isteyeceksiniz ama 2 yıldır Scala ile geliştirme yapan birisi olarak tür çıkarımını kullanmak konusunda şu tavsiyeleri verebilirim:

  1. Tür çıkarımı kullanacaksanız isimlendirmelerinizde mutlaka okuduğunuzda size tür hakkında da fikir verecek bir isim seçin. Sonra kodunuz büyüdükçe
    val temp = Foo.bar

    gibi bir koda bakıp

    Bu ‘temp’ değeri ne şimdi? Türü ne bunun?

    diye saç baş yolabiliyorsunuz çünkü. 🙂

  2. Metodlarınızın dönüş türlerini yazmayıp tür çıkarımı yapmak istiyorsanız bir kere daha düşünün. Yukarıda bahsettiğim gibi kodunuz büyüdükçe metodun ne döndürdüğünü bir bakışta anlayamamak sizin için sorun olmaya başlayacaktır. Eğer metod küçükse ve ne döndürdüğünü bir bakışta görebiliyorsanız tür çıkarımı kullanmakta bir sakınca yok.

Sonuç

Bu yazımda Scala’daki türlerden, birbirleriyle ve Java’daki türlerle olan ilişkilerinden ve tür çıkarımından bahsetmeye çalıştım. İnşallah anlaşılır olmuştur.

Bir sonraki yazıda görüşmek üzere. Hoşça kalın!

Kaynaklar

Türlerle ilgili şema için: http://docs.scala-lang.org/tutorials/tour/unified-types.html