Kotlin’de Yüksek Seviye Fonksiyonlar (Higher-Order Functions)

Image designed by Author on Canva

2010’da Oracle, Sun Microsystems’ı satın aldığından beri Oracle ile Google arasında bir Java savaşı sürmekteydi. Oracle, Java’nın ücretsiz olmayan özelliklerini ücret ödemeden kullanmakla suçladığı Google’a büyükçe bir dava açtı falan filan… Hikâyeyi biliyorsunuzdur, sanırım hepsini anlatmama gerek yok. GoogleIO 2017 etkinliğinde Kotlin, Google tarafından resmî programlama dili ilan edildi. O gün bugündür, Android geliştiricileri olarak Kotlin öğreniyoruz.

“Kotlin, fonksiyonel dil özelliklerine sahiptir,” cümlesini -ben dâhil- pek çok Kotlin ve Android hayranından duymuş olabilirsiniz. “Nedir yani bu fonksiyonel dil özellikleri? Haydi örnek ver, haydi!” diyorsanız toplanın yamacıma. Bu yazıda, Kotlin’in fonksiyonel dil özelliklerinden birisi olan yüksek seviye fonksiyonların kullanımını anlatacağım.

Fonksiyonel dillerde fonksiyonlar özel ve tektir. Literatürde bu, first class tabiri ile ifade edilir. Yani fonksiyonlar; String, Integer veya herhangi bir sınıf ile aynı seviyededir. Fonksiyonlara parametre olarak String veya Integer yollayabildiğimiz gibi, first class fonksiyonlara sahip dillere de parametre olarak fonksiyon yollayabiliriz. Yani, fonksiyona parametre olarak fonksiyon yolluyoruz. Aynı şekilde, başka fonksiyonlardan return tipi olarak fonksiyon da döndürebiliriz.

Kotlin gibi fonksiyonel dillerde fonksiyonlar Java’nın aksine herhangi bir sınıf içerisinde yer almak zorunda değillerdir. Fonksiyon çağıran fonksiyonlara yüksek seviye fonksiyonlar denir ve fonksiyonel diller, yüksek seviye fonksiyon olma özelliğine sahiptirler. Kotlin de doğal olarak bu özelliğe sahiptir. Ayrıca fonksiyonel dillerde fonksiyonlar veri yapılarında ve değişkenlerde tutulabilirler.

Özet olarak Higher-Order Functions, Kotlin’in gözde özelliklerinden birisidir. Şimdi bunu önce küçük bir örnek ile daha iyi açıklayacağım, ardından ise Android’deki kullanımını göstererek açıklamamı pekiştireceğim. İzninizle, başlayalım:

Parametre olarak fonksiyon kabul eden bir fonksiyonun tanımlanması aşağıdaki gibi yapılır:

code
fun passMeFunction (text: String, function: ( ) -> Unit) {
    // do something
    // execute the function
    function()
}
code
passMeFunction ( “text” ) { print ( it ) }
passMeFunction ( “text” ) { s -> print ( s ) }
passMeFunction ( “text” , { print ( it ) } )
passMeFunction ( “text” , { s -> print ( s ) } )
passMeFunction (“text”, ::print ) // KISS : Keep It Small Stupid hell yeeaa

Ben açıkçası bu özelliğe ihtiyaç duyduğumun farkında bile değilmişim. Kullanmaya başladığımda anladım ki pek çok yerde higher-order function kullanabilirim. Özellikle orta ve daha büyük ölçekte bir proje yazarken pek çok defa aynı koda başka Activitylerde, Fragmentlarda, sınıflarda ihtiyaç duyarsınız. Mesela, kullanıcı silme işlemi kullanıcı listesinden de kullanıcı detay sayfasından da gerçekleştirilebilir ancak kopyala-yapıştır yapmaya başlarsanız büyük projede ilerleyen safhalarda boğulabilir, çöp kod üretebilir ve kod yönetimini kendi elinizle zorlaştırabilirsiniz.

Screenshot by author

Screenshot by author

Kullanım Örneği:

Gerçek yaşam örneğine gelirsek: Peakup’ta; çalışanların hızlıca izin alabilmelerini, izin sürecini takip edebilmelerini, takım liderlerinin ve insan kaynaklarının da bu süreci kolayca yönetebilmelerini sağlayan bir Android uygulamamız var: Leave Management. Bu uygulamamızı yazarken bir sorunla karşılaştım: İzin silme, onaylama ve reddetme işlemleri için hemen hemen aynı fonksiyonu yazdığımı ve gereksiz kod tekrarı yaptığımı fark ettim. 3 operasyonda da izin detay DialogFragment’ını önce kapatıp sonra “Silmek / reddetmek / onaylamak istediğinize emin misiniz?” diye soran bir dialog çıkartıyor, kullanıcı onay butonuna dokununca da ilgili fonksiyon aracılığı ile backend’e request yolluyordum. Yazılan fazladan kodu fark ettiğimde aklıma Higher Order Function özelliği geldi çünkü 3 fonksiyonda da sadece Dialog’da yazan text ve çağrılan request fonksiyonu farklıydı. Şöyle bir fonksiyon yazdım:

code
private fun showAlertAndOperate(
    @StringRes stringId: Int, 
    leave: Leave, func: () -> Unit
) {
      leaveDetailsDialogFragment.dismiss()

      val text = getString(stringId, leave.User.getNameSurname())
      showAlertDialog(text, DialogInterface.OnClickListener {_, which ->
           if (which == DialogInterface.BUTTON_POSITIVE) {
                func() /* func() is sent as parameter. When dialog shows up 
                          and user tap OK button, func() is fired here */
           }
      })
}

“showAlertAndOperate” ismini verdiğim bu fonksiyon, 3 adet parametre alıyor. İlk parametresi bir Int ancak annotation olarak StringRes’e sahip. Yani bu fonksiyona göndereceğiniz ilk parametre R.string.xxx gibi bir string id olacak. İkinci parametre bir Leave POJO objesi. Son parametre ise işlemi yapacak olan fonksiyon. Yani; son parametrede eğer izin silme işlemi yapacaksak izin silme request’ini, izin onaylama işlemi yapacaksak onaylama request’ini yollayan fonksiyon çağırılacak.

“showAlertAndOperate” fonksiyonu, önce izin detay dialog fragment’ını kapatıyor. Ardından normal Dialog içerisinde gösterilecek olan metni StringRes ve Leave parametrelerini kullanarak hazırlıyor. Ardından bu metni showAlertDialog fonksiyonuna yollayarak Global alertdialog’un çıkmasını sağlıyor. Dialog’un onay butonuna basıldığı takdirde ise fonksiyona parametre olarak gönderilen fonksiyon çalışıyor.

code
override fun onLeaveApprove(leave: Leave)  =
    showAlertAndOperate(R.string.are_you_sure_to_accept, leave) {
        confirmLeave(leave, MODE_ACCEPTED) 
    }

override fun onLeaveDecline(leave: Leave)  =
    showAlertAndOperate(R.string.are_you_sure_to_decline, leave) {
        confirmLeave(leave, MODE_DECLINED) 
    }

override fun onLeaveDelete(leave: Leave) =
    showAlertAndOperate(R.string.are_you_sure_to_remove, leave) { 
        deleteLeave(leave) 
    }

Yukarıda gördüğünüz fonksiyonlar ise izin detay dialog fragment’ına ait interface içerisinde bulunan buton onClick işleminde çalışan callbackler. Gördüğünüz üzere; onLeaveApprove ‘da confirmLeave fonksiyonu accepted modunda, onLeaveDecline ‘da confirmLeave fonksiyonu declined modunda çağrılmış. “onDelete”te ise deleteLeave() fonksiyonu çağrılmış. “showAlertAndOperate” fonksiyonunda belirtilen func() fonksiyonu yerine bu fonksiyonlar çağrılacak.

Tabii kodu kopyala-yapıştır yapmamak için pek çok farklı yöntem de mevcut. Bu yazıda bu yöntemlerden sadece birinden bahsedebildim. “En güzel yöntem budur, golden key’dir,” gibi iddialara sahip değilim. Sadece bu seferlik bunu tercih ettim ve sizlerle paylaşmak istedim.

Bu yazı aslında daha önce çalıştığım bir şirket olan Peakup blog da yayınlanmıştır. Orada ekran görüntüleri bozulduğu ve yazının sahibinin ben olduğum belli olmadığı için buraya aktarmayı tercih ettim.

Bol kodlu günler dilerim.