Kotlin’de Anonymous & Higher Order Fonksiyonların Kullanımı

Mert Ozan Kahraman
5 min readJun 22, 2023

--

Herkese merhabalar tekrardan. Umarım keyifleriniz yerindedir, değilse de dert etmeyin çünkü bu yazımda, Anonymous ve Kotlin’in kavraması belki de en zor konularından biri olan Higher Order fonksiyonlara değineceğim.

Aslında arka planda farkında bile olmadan kullandığımız Higher Order fonksiyonları ve Anonymous fonksiyonları anlamak için ilk önce değinmemiz gereken bir şey daha varsa o da Lambda Expressions. Lambda Expressions, ilk bakıldığında göze zor veya anlamsız gelebilir, ancak kullanımı gayet kolay ve gereklidir. İsterseniz hiç beklemeden Lambda Expressions’a değinerek başlayalım.

ƛ Lambda Expressions

Yukarıda da bahsettiğim gibi Lambda Expressions, ilk bakıldığında anlamsız gelir ancak kavramını anladıktan sonra, gerçekten bize kullanışlı bi yapı sunar. Lambda Expressionlar aynı zaman da Function Literal olarak da adlandırılır. Function Literal kavramından Anonymous Functions kısmında tekrardan ve daha detaylı bir şekilde değineceğim. Şimdi Lambda Expressions nasıl tanımlanır ve kullanılır, örnekler üzerinden konuşalım.

fun firstLambdaExpression(parameter: (Int, Int) -> Int) {
parameter(20, 30)
}

firstLambdaExpression{int1 , int2 ->
println(int1 * int2) // Unit type
//---Some-Ops--
int1 + int2 // Integer type
}

Yukarıda da görebilceğiniz gibi fonksiyonumuzu tanımlayıp aynı diğer fonksiyonlar gibi fonksiyonumuzu çağırdık. Ancak farklı yaptığımız birkaç şey var. Mesela fonksiyonu tanımlarken parametremizi (Int, Int) -> Int şeklinde vermişiz peki bu ne demek?

(Int, Int) -> Int

1. parametre Int, 2. parametre Int ve bu fonksiyonun dönüş değeri Int olmalı.

Bu ifade de; “parameter” isimli parametremizin değerini verirken iki değer vereceğiz, iki değerimiz de Integer tipli olacak- neden çünkü, (Int, Int) olarak belirtmişiz- ve bunun dönüş değeri de Integer veri tipiyle olacak şeklinde aklımızda tutabiliriz. Fonksiyonumuzun içinde de bu parametremize, ileride çağrıldığında kullanılcak değerlerini veririz.

Fonksiyonumuzu çağırdığımızda ise yukarıda belirttiğimiz parametrelere tekrardan değer ataması yapmadan, constructor parantezlerini koymadan, süslü parantezlerimizi açarız. Ancak parantezlerimizi açtıktan sonra, yukarıda değerlerinizi ve türlerinizi verdiğimiz değerleri, fonksiyon içi kullanmak için isim olarak belirtiriz ( int1 => 20 , int2 => 30 ).

Fonksiyonda işlemlemlerimizi yaparken, fonksiyon parametremizde pointer işaretiyle ( -> ) belirttiğimiz tip ( … -> Int ) olması gerekiyor. Lambda expressions son ifadesi dönüş tipidir return yazmamıza gerek yok. Son ifadenin tipi alınır. Yani son ifadenin tipinin belirttiğimiz değer olmasına dikkat etmeliyiz.

Kafalarımız karıştı diye düşünüyorum… Güzel. Hiç telaş yok birkaç örnek ile çok daha iyi anlayacağız. Hemen bir sonraki örneğe geçelim.

💫 Bu sefer de iki parametreyi bir parametreye düşürelim

fun lambdaFunc(double: (Double) -> Unit){
double(20.02)
// Burada parametre olarak bir parametre alacağımızı ve bunun Double tipinde
// Dönüş tipininde Unit -yani değer döndürmeyecek- olacağını belirtiyoruz.
}

lambdaFunc{
println(it.toString) // print fonksiyonu Unit tiplidir
}

İlk yaptığımız fonksiyona benzer ancak bu sefer tek parametre verdik ve dönüş tipini Unit olarak belirledik.

Dipnot: Tek parametreli bir Lambda expression çağırdığımız zaman, süslü parantezden sonra değişkeni belirten bir isim vermemize gerek yoktur. Direkt “it” yazarak parametre değerine erişilebilir.

Hadi, şimdi daha kullanışlı bir kullanım deneyelim.

fun sumOperation() = { a: Int, b: Int ->
val sumValue = a + b
println(sumValue)
}

sumOperation().invoke(40,22)

Bu sefer başka bir yol deneyerek yukarıda, iki fonksiyonda yaptığımızı tek bir fonksiyon da topladık ve değer atamasını da çağırdığımız zaman yaparak okuması daha kolay bir hale getirdik.

Yukarıda da görüldüğü gibi Lambda Expressions, Type Inference’a uygundur, yani bir geri dönüş değeri belirtme zorunluluğu yoktur.

Son bir kullanımımız kaldı…

val chainOps : ( String, Double) -> Unit = { s: String, d: Double ->
println("Have a ${s.toInt() + d.toInt()} Nice Day")
}

chainOps.invoke("300",65.00)
chainOps("300",65.00)

Bu şekilde de kullanarak fonksiyonu bir değişkene atayabilir ve çağırabiliriz. Ancak değişkenin değerini belirtmemiz gerektiğini unutmamak lazım.

Yukarıda fonksiyonu iki şekilde çağırmını görebilirsiniz. İki yolda doğrudur ancak invoke ile çağırmanın Null Check yapma avantajı bulunur.

Umarım Lambda Expressions kısmını anlatabilmişimdir. Çünkü bu kavram, fazlasıyla önemli ve kullanımı fazla olan bir kavramdır. En yakın örneği de Higher Order Fonksyionlar diyebiliriz. Ancak onlara geçmeden önce değinmemiz gereken bir fonksiyon türü daha var.

⏯ Anonymous Functions

Lambda fonksiyonlar return type’ı açık( explicit ) olarak belli edemezler. Burada da devreye güzide fonksiyonumuz Anonymous Functions devreye giriyor. Bu fonksiyonun normal fonksiyonlardan farkı isimsiz olmasıdır. Single Expression şekilde yazılabilir ve Type Inference’a uygundur, return tipini açık olarak belirtemize gerek yoktur.

val anonymousFunction = fun(element: String): Boolean {
return element.length > 4
}

anonymousFunction.invoke("Hello!") // true

Görebilceğiniz üzere gayet kolay bi kullanımı var. Fonksiyonumuzu belirttik ve invoke() ile fonksiyonumuzu çağırdık.

val checkIsBigger = fun(number: Int) :Boolean{
return number > 3
}

val numbers = intArrayOf(1, 2, 3, 4, 5, 6)

numbers.filter(checkIsBigger) // (4,5,6)

// filter fonksiyonun normal kullanımı
numbers.filter{
it > 4 // (4,5,6)
}

// anonymous fonksiyonu parantez içine vererekte aynı sonucu alabiliriz
numbers.filter(fun (number: Int) :Boolean = number > 3 ) // (4,5,6)

Bu sefer oluşturduğumuz fonksiyonumuzu, Arrayler ile kullanabileceğimiz .filter fonksiyonuna şartlarını sağlamak için veriyoruz ve yukarıda görebileceğiniz gibi üç doğru ve farklı yol ile aynı sonucu alabiliyoruz. Anonymous fonksiyonun burada kullanılma amacı için şartları fonksiyon ile paketleyip, .filter fonksiyonuna vererek daha okunabilir bir hale getirebiliriz.

Functions Literal: Bir fonksiyon tanımlanmadan anlık olarak kullanıldığı yapıya functions literal diyoruz. Anonymous Functions ve Lambda Expression da birer Functions Literaldır.

Lambda Expression ve Anonymous Functions anladığımıza göre beklenen an olan Higher Order Functions’a geçme zamanı geldi.

⬆ Higher Order Functions ⬇

Parametre olarak fonksiyon alabilen ve/veya fonksiyonu döndürebilen fonksiyonlara Higher Order fonksiyon denir. Kullanıldığı zaman çok yararlı olan ancak ilk başlayanlar için çoğu zaman göze hep zor gelen Higher Order fonksiyonlar için örnekler üzerinden incelediğinizde gerçekten çok kolay olduğunu göreceksiniz. Hemen ilk örneğimiz ile isterseniz incelemeye başlayalım.

fun higherOrderFunction(num1: Int, num2: Int, operation:(Int,Int) -> Unit){
operation.invoke(a,b)
}

higherOrderFunction(num1=20, num2=30, {n1, n2->
println(n1 * n2)
})

higherOrderFunction(20,30){n1, n2->
println(n1 * n2)
}

Yukarıda oluşturduğumuz Higher Order fonksiyon için basit olarak; iki tane parametre vermişiz ve operation isimli bir Lambda Expression tanımlamışız. Fonksiyon içinde ise, tanımladığımız bu iki parametreyi operation’ı invoke ederek parametre olarak atarız Fonksiyonumuzu çağırdık -İki kere çağırıldığını fark etmişsinizdir, iki yol da doğrudur ancak Kotlin’in ikinci yolu önerdiğini belirmiş olayım-, verdiğimiz parametrelere değer atamasını yaptıktan sonra, Lambda Expression’ı süslü parantezler ile belirttikten sonra belirttiğimiz geri dönüş değerine dikkat ederek işlemlerimizi yapabiliriz.

Aslında temelde bu kadar kolay olan Higher Order fonksyionlar için, haydi birkaç örnekle ile de pekiştirelim. Hem birkaç detaya da değinmiş oluruz.

fun higherOrderFuncWithThreeParameter(
string :String, integer: Int, double: Double,
operation: (String, Int, Double) -> String
){
operation(string, integer, double)
}

higherOrderFuncWithThreeParameter("Hello",20,50.08){ s, i, d ->
s + ( i.toDouble + d ).toString
}

higherOrderFuncWithThreeParameter("World",22,34.98){ s, i, d ->
( i.toDouble + d ).toString + s
}

Bu sefer parametre sayısını arttıralım ve tipleri değiştirelim. Aslında değişen hiçbir şey yok sadece bir parametreye daha değer vermemiz gerekti. Görebilceğiniz üzere bu sefer bir kere daha kullandım fonksiyonu farklı değer ve farklı sonuç verecek şekilde kullandık ancak tek bir şey aynı o da kullanım şekli. İşte Higher Order fonksiyonun bize sağladığı avantajlardan en basiti.

fun singleParameterHigherOrder(string: String, operation: (String) -> String){
operation(string)
}

singleParameterHigherOrder("Hello World!"){
it + "and Mars :)"
}

Yukarıda göründüğü gibi tek parametre ile de bu fonksiyonu kullanabiliriz.

fun higherOrderFuncWithoutParameter(operation: () -> Unit) {
operation()
println("Higher Order Function launched.")
}

higherOrderFuncWithoutParameter{ }

Bu sefer ise farklı bir şey deneyerek parametresizsiz bir Higher Order yazalım. Hiç parametre vermeden sadece dönüş değeri vererekte Higher Order fonksyion çağırabiliriz.

Vee… bu yazının da sonuna gelmiş bulunduk. Biraz uzun bir yazı oldu ancak önemli bir konu olduğu için detaylı ve açıklayıcı bir şekilde anlatmak hiç bir yeri atlamak istemedim. Umarım yararlı olmuştur. Eleştiri, yorumlarınız ya da sorularınızı, sosyal medya hesaplarımdan veya yorumlar kısmından bana iletebilirsiniz. Herkese iyi günler ve iyi kodlar dilerim. 😁

Twitter: https://twitter.com/meetozan

Github: https://github.com/meetOzan

Linkedin: https://www.linkedin.com/in/mert-ozan-kahraman-578b23234/

Hoşçakalıınn 👋🏻

Kaynakça

➡ Halil Özcan

--

--