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

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

  1. Merhabalar, güzel yazı.
    Benim iki sorum vardı:

    1) Python’dayım ben, dinamik tür olduğu için çok yavaş bir dil. Anladığım Scala bize bu seçeneği sunuyor. Eğer ben Scala’da dinamik türde kodlarsam bu statik türde kodlamaya göre yavaş bir performans mı sergiler?

    2) Bu dinamik değişkenler sonradan tür değiştirince bir exception alabiliyor muyum, yoksa almıyor muyum? Yani:
    val a = 3
    a = “b”
    Hata verir mi?

    Liked by 1 kişi

    • Merhaba Eray, teşekkür ederim.

      1) Scala dinamik türlü değil, statik türlü.Sana türü yazmama imkanı sağlıyor ve yazmadığın türü anlamak için tür çıkarımı yapıyor. Bu işlem derleme sırasında ek bir maliyet getiriyor tabi ki. Aynı kodu bütün türleri açıkça belirterek yazmak ile tüm türleri atlayıp tür çıkarımına bırakmak arasında derleme aşamasında performans farkı olacaktır. İyi bir performans için türü yazmadan geliştirmek ile türü açıkça yazarak geliştirmeyi mümkün olduğunca dengeli kullanmak gerek.

      2) Kısa cevap; verir. 🙂 Senin örneğin için ilk sorun şu, val kelimesiyle sabit değer tanımlıyorsun. Sonradan a = “b” gibi bir atama zaten yapamazsın. Eğer val yerine var kullanacak olursan a’ya sonradan atama yapabilirsin ama türü tutmayacağı için yine hata alırsın.

      İnşallah yeterli olmuştur. 🙂

      Beğen

mehmetakiftutuncu için bir cevap yazın Cevabı iptal et