Kotlin’de Higher Order Fonksiyonlar

Abdulkadir Kara
9 min readJul 11, 2024

--

Lambda expression ve anonim fonksiyonlar, Kotlin’de fonksiyonel programlamanın temel taşlarını oluşturur ve bu nedenle higher order fonksiyonları anlamak için önemlidir. Bu sebeple lambda ifadeleri ve anonim fonksiyonları anlatarak başlayalım, ardından higher order fonksiyonları detaylı bir şekilde ele alalım.

Lambda İfadeleri

Lambda ifadeleri, Kotlin’de kısa ve anonim fonksiyonlardır. Lambda ifadeleri, bir kod bloğunu basitçe tanımlamanıza olanak tanır. Lambda ifadeleri, genellikle koleksiyonlarla çalışırken kullanılır ve -> işareti kullanılarak tanımlanır.

val sum: (Int, Int) -> Int = { x, y -> x + y }

fun main() {
val result = sum(4, 5)
println("Sum: $result") // Output: Sum: 9
}

Anonim Fonksiyonlar

Anonim fonksiyonlar, adlandırılmamış fonksiyonlardır ve fun anahtar kelimesi ile tanımlanır. Lambda ifadelerine benzer şekilde kullanılır, ancak daha uzun ve karmaşık ifadeler için uygundur.

val multiply = fun(x: Int, y: Int): Int { return x * y }

fun main() {
val result = multiply(4, 5)
println("Product: $result") // Output: Product: 20
}

Higher Order Fonksiyonlar

Higher order fonksiyonlar, başka fonksiyonları parametre olarak alabilen veya fonksiyon döndürebilen fonksiyonlardır. Bu, fonksiyonel programlamanın temelini oluşturur ve daha esnek, modüler ve yeniden kullanılabilir kod yazmayı sağlar.

Bu kısa higher order fonksiyon tanımından sonra 2 sayıyı alıp matematiksel işlem yapan normal bir fonksiyon yazıp onun üzerinden higher order fonksiyon’a çevirerek anlayalım.

fun main(){
calculateAndPrint(2,4,'-')
calculateAndPrint(3,2,'*')
}
fun calculateAndPrint(numberOne: Int,numberTwo: Int,operation: Char){
val result = when(operation){
'+'->{
numberOne + numberTwo
}
'-'->{
numberOne - numberTwo
}
'*'->{
numberOne * numberTwo
}
'/'->{
numberOne / numberTwo
}
else->{
numberOne + numberTwo
}
}
println("Result : $result")
}

Bu fonksiyonu higher order’a çevirirsek;

fun calculateAndPrint2 (numberOne: Int, numberTwo: Int, operation: (numberOne: Int, numberTwo: Int) ->Int){
val result = operation(numberOne,numberTwo)
println("Result : $result")
}

Bu operation bir değişken,bir parametre olmasın, iki tane numberOne ve numberTwo Int tipinde parametre alıyor olsun ve geri dönüşü tipi de int olsun.
Biz burda operation‘ u bir fonksiyon gibi tanımlıyoruz. Higher order fonksiyonlar da aslında bu demek.

Eğer elimizde bir fonksiyon varsa ve bu fonksiyon parametre kısmında başka bir fonksiyonu alıyorsa(fonk geri dönüş değerini veya return ettiği değeri değil mevzu direk fonksiyonu body’sini oraya yazabiliyor olmamız) bunlara higher order fonksiyon denir.

operation:(numberOne:Int,numberTwo:Int)->Int aslında fonksiyon yazımından pek bir farkı yok

fun operation (numberOne: Int, numberTwo: Int ): Int {..} diyecektik
burada calculateAndPrint2 içinde operation: dediğimiz için sonda geri dönüş tipini lambda( -> ) ile gösteriyoruz.

calculateAndPrint fonksiyonu ile calculateAndPrint2 fonksiyonlarına baktığımızda açıkta kalan bir konu var;

calculateAndPrint fonksiyonunda result’ı almak için when ile işlem yaptığımız kısım vardı calculateAndPrint2'de operation(numberOne,numberTwo) operation diye çağırdığımız/call ettiğimiz fonksiyon nerde bu işlemi yapacak? When kısmına denk gelecek olan kod operation(numberOne,numberTwo) burda bu fonksiyonun kendisidir.

main fonksiyonun içinde calculateAndPrint2 fonksiyonu çağırdığımızda yine 2 tane sayı veriyoruz, üçüncü parametre olarak fonksiyon vermemiz lazım. Çünkü operation:(numberOne:Int,numberTwo:Int)->Int buranın kendisi komple bir fonksiyon. Dolayısıyla fonksiyonu çağırdığımızda 3. parametre olarak vereceğimiz şey basitçe fonksiyonun body’sidir.

fun main(){
calculateAndPrint2(3,5,{numberOne,numberTwo-> numberOne + numberTwo})
calculateAndPrint2(88,56,{numberOne,numberTwo-> numberOne * numberTwo})
}

main fonksiyonda çağrımlara baktığımızda calculateAndPrint fonksiyonunda 2sayı ve işlemin karakterini veriyorduk ve fonksiyonun içinde işlemi yapıyorduk. calculateAndPrint2 fonksiyonunda ise higher order fonksiyonumuzun içine gömmüş olduk kodumuzu. Yani aslında when’deki her bir şartı kopyaladık ve calculateAndPrint2 fonksiyonu çağrımıdaki 3.parametre yerine her gereken çağırım’da yapıştırdık gibi.

Higher order fonksiyonlar neden daha güzel dersek mesela her iki fonksiyonda da yaptığımız işlemleri düşündüğümüzde modunu alma, logaritmasını alma, trigonomtrik bir değerini hesaplama… vb. yeni özelikler ekleyeceğimiz zaman calculateAndPrint fonksiyonunda when case’ine de eklememiz lazım çağrımdan önce ama calculateAndPrint2 fonksiyonunda çağrım yaparken 3. parametre yerinde direk işlemi yazabiliriz sadece. When kısmı aslında yönetilebilir değil çünkü dediğim
gibi daha başka özellikler de eklenebilir ve bu durumda da büyür.

Debug ile baktığımızda calculateAndPrint2 fonksiyonu çağırdığımız yere debug koyarsak. Önce fonksiyon çağrılıyor call edildiğini gördüğü için bunun tanımının yapıldığı yere gidiyor normal fonksiyon davranışı olarak. call edip fonksiyona gittiğimizde operation fonksiyonu da orda call ediliyor. dolayısıyla operation’un da nerde tanımını yaptıysak oraya zıplamamız lazım operation normal fonksiyon olsaydı. burda higher order fonksiyon olduğu için operation call edildiği zaman zıplayacağımız yer de bunun body’sinin olduğu yerdir o da calculateAndPrint2 fonksiyonunu call ettiğimiz yerdeki 3.parametredir. Fonksiyon body’sine gidip işlemi yaptıktan sonraprintln yapacaktır.

Higher order fonksiyonlar parametre olarak fonksiyon body’si parametre alan fonksiyonlardır. Bir fonksiyon eğer ki paremetresine başka bir fonksiyonun body’sini alabiliyorsa bu fonksiyona higher order fonksiyon diyoruz. Higher order fonksiyonumuz bizim calculateAndPrint2 fonksiyonudur operation higher order fonksiyonumuzun içerisindeki düz bir fonksiyon.

Higher order fonksiyona kaç tane parametre olarak fonksiyon yazarsak yazalım, eğer ki oraya yazdığımız fonksiyonlar kendileri de numberOne, numberTwo gibi parametre olarak başka fonksiyon alabilirler ya da ->Int dediğimiz gibi başka bir fonksiyonu return edebilirler.

Şimdi bir de higher order fonksiyonlar arka planda nasıl çalışıyorlar diye show kotlin bytecode ile java koduyla beraber kıyaslayalım.

kotlin dilinde higher order
java’daki karşılığı kotlin kodunun

Higher order fonksiyonların 2 tane durumu vardır;

  1. Bir fonksiyon parametre olarak başk bir fonksiyona verildiyse,
  2. Bir fonksiyon başka bir fonksiyonun geri dönüş tipiyse

Bu 2 tanımdan biri geçerliyse fonksiyon higher order fonksiyon olur.

calculateAndPrint2 fonksiyonumuzun geri dönüş tipi unit yani bir şeyi geri döndürmüyor ama parametre olarak bir fonksiyon aldığı için higher order fonksiyon olur.
operation fonksiyonu neden bir higher order fonksiyon değil peki geri dönüş tipi int ayrıca parametre olarak da primitive tipleri tek almış.

Mesela calculateAndPrint2 fonksiyonu yazılırken parametre kısmı şöyle olsaydı;

fun calculateAndPrint2( numberOne: Int,numberTwo: Int,operation:(numberOne:Int,numberTwo:Int,foo:()->Unit)->Int){
//...
}

burda calculateAndPrint2 bir higher order fonksiyon olduğu gibi operation da parametre olarakfoo adlı bir fonksiyon aldığı için higher order fonksiyon olurdu.

fun calculateAndPrint2( numberOne: Int,numberTwo: Int,operation:(numberOne:Int,numberTwo:Int)->()->Int){
//...
}

First-Class Citizen Kavramı

Kotlin’de fonksiyonlar, birinci sınıf vatandaşlar (first-class citizens) olarak kabul edilir. Bu, fonksiyonların değişkenlere atanabileceği, başka fonksiyonlara parametre olarak geçirilebileceği ve fonksiyonlardan döndürülebileceği (geri dönüş değeri olabileceği) anlamına gelir.

Higher order fonksiyonlar basitçe bir fonksiyona parametre olarak verilen fonksiyonlardır. Parametre olarak vermekten kasıt, fonksiyonun çağrımının parametre kısmında yapılması değil, fonksiyonun gövdesinin (body) yani süslü parantezler arasında kalan görev alanının başka bir fonksiyona parametre olarak verilmesidir.
Yapısal olarak;

fun higherOrderFunction ( normalFunction : (message:String)-> Unit){
normalFunction ("kotlin")
}
fun main(){
higherOrderFunction ({message->
println("Message: $message")
})
}

Higher order fonksiyonlara verilen normal fonksiyonları tanımlarken 3 farklı yol vardır;

  1. Bir değişkene atayarak fonksiyon tanımlayabilirsiniz.

Bu durumda, süslü parantezlerin yanına higher order fonksiyonun aldığı parametreler lambda okundan önce aralarına virgül konularak yazılır. Higher order fonksiyon tek parametre alıyorsa, bu parametreleri yazmak zorunda değilsiniz. Bu durumda, higher order fonksiyon size “it” kelimesi ile higher-order fonksiyonun parametresi tipinde bir değişken sunar.

Bir higher-order fonksiyon aslında bir fonksiyonu parametre olarak alır ve genellikle bu fonksiyon, lambda ifadeleri kullanılarak tanımlanır. Lambda ifadesi içinde, “it” anahtar kelimesi ile bu parametreye erişebilirsiniz. Yani, higher order fonksiyon burada bir değişken gibi gözükse de, aslında bir fonksiyondur.

val higherOrderFunction = { surname: String->
"surname : $surname"
}

veya main fonksiyondaki calculateAndPrint2 higher order fonksiyon çağırımımızı böyle bir yapalım;

fun main(){
val sumFunction ={numberOne: Int, numberTwo: Int ->
numberOne + numberTwo
}
calculateAndPrint2(4,6,sumFunction)
}

2. İsmi olmayan “anonymus function” tanımlamaları da higher order fonksiyon olarak normal bir fonksiyona parametre olarak verilebilir.

val anonymusFunction= fun(surname: String): String{
return "surname : $surname"
}

anonymus functionexpression kullamını da yine higher order fonksiyon olarak normal bir fonksiyona parametre olarak verebiliriz.

val anonymusFunction2 = fun(surname : String ) : String = "surname : $surname"

Hadi bunları da calculateAndPrint2 fonksiyon çağrımında yine kullanalım;

fun main(){
val minusFunction = fun(numberOne: Int, numberTwo: Int) : Int{
return numberOne - numberTwo
}
calculateAndPrint2(4,6,minusFunction)

val multiplyFunction = fun(numberOne: Int, numberTwo: Int) : Int = numberOne * numberTwo
calculateAndPrint2(4,6,multiplyFunction)
}

3. Higher order fonksiyonla aynı parametre sayısına sahip ve bu parametrelerin hepsinin tipleri higher order fonksiyonun parametre tipleri ile aynı ise,bu normal fonksiyon da higher order fonksiyon olarak normal bir fonksiyona parametre olarak verilebilir.

Bunu yapmak için sadece başına:: işarti koymak yeterlidir.

fun main(){
calculateAndPrint2(24,6,::divFunction)
calculateAndPrint2(48,6,::div2Function)
}
fun divFunction(numberOne: Int, numberTwo:Int) : Int{
return numberOne / numberTwo
}
fun divFunction2(numberOne: Int, numberTwo:Int) : Int = numberOne / numberTwo

Higher order fonksiyon bir fonksiyon bekliyor return değeri beklemiyor o yüzden şöyle yazamayız;

fun main(){
calculateAndPrint2(24,6,divFunction(12,4))//3.parametre artık bir fonksiyon body'si değil 12/4 işlemi sonucu olan 3'ü return eden int değer olur
calculateAndPrint2(48,6,div2Function(43,56))
}

Böyle yapıyor olursak return ettiği değeri alıyoruz demektir. biz ama fonksiyonun body’sini istiyoruz o yüzden ::fonksiyonismiyazıyoruz. Böyle kullanmak için unutmamamız gereken şartımız var fonksiyonun(burda divFunction) parametre sayısı higher order da beklenen fonksiyonun parametre sayısıyla, tipiyle ve geri dönüş tipiyle aynı olmalı!
Parametrelerin sayısı, tipleri, fonksiyonun dönüş tipi de aynı olmalı.

Bence en temiz gözüken kullanım şekli bu çünkü kodun okunaklılığını, bakımını, test yazılabilmesini daha kolay sağlıyor. Böyle fonksiyonla kullandığımızda business logic ile main fonksiyonu da ayırmış oluyoruz hem, diğer türlü sumFunction ve minusFunction main fonksiyonun içinde yazıp öyle kullanıyorduk. Mesela istersek burdaki business logic’i viewmodel’e koyabiliriz, viewstate’e koyabiliriz, istersek use case’lere koyabiliriz, bu business logic’i nereye koyup ayırmak istiyorsak ayırabiliriz. sumFunction ve minusFunction’a test yazabilmek için main fonksiyona test yazmamız gerekir ama divFunction ayrı bir fonksiyon olduğundan tek başına test yazılabilir.

sumFunction fln değişken gibi gözükseler de fonksiyon oldukları için invoke() fonksiyonunu çağırabiliyoruz. invoke fonksiyonu demek calculateAndPrint2 fonk’daki operation(numberOne,numberTwo) call ile aynı demek.

val result = operation(numberOne,numberTwo)

bunun yerine şöyle de yazabiliriz;

val result2 = operation.invoke(numberOne,numberTwo)

invoke’ı bazen neden kullanmamız gerekecek invoke’ın önüne gelecek şeyler(burda operation) nullable olabilir. Nullable olduğu durumda operation?.invoke veya opration!!.invoke yapabiliriz.

Eğer higher order fonksiyonumuz son parametre ise bunu şöyle süslü parantezi yuvarlak parantezlerin dışına da yazabiliriz.

fun main(){
calculateAndPrint2(3,5){numberOne,numberTwo-> numbeOne + numberTwo}
}

Higher order fonksiyonlar default değer alabilirler. Bunun için basitçe süslü parantezler açmak yeterlidir. Dikkat edilmesi gereken konu bu süslü parantezler içine higher order fonksiyon parametre bekliyorsa bunlar verilmelidir.

fun printUserInfo(name: String, getUserName : (surName: String) -> String = { surName -> ""}, age: Int): Unit {
println("name: $name , age : $age")
println(getSurName("KARA"))
}

Normalde higher order fonksiyonları kullanırken,higher order fonksiyonun beklediği fonksiyondaki parametreleri belirtirken bir isim vermemize gerek yok, normalde şu kullanımda görürsünüz;

fun calculateAndPrint2(numberOne: Int,numberTwo: Int, operation : ( Int , Int ) -> Int ){
val result = operation(numberOne,numberTwo)
println("Result : $result")
}

Zaten bundan önceki parametrelerin ismi olduğu haldeki parametreleri kullanmıyoruz hiç bir yerde. operation’a bu değerleri veriyoruz ama numberOne ve numberTwo calculateAndPrint2 fonksiyonundaki birinci ve ikinci parametredeki isimlerdir operation(numberOne,numberTwo) burda kullandığımız.

Fonksiyonlardan bildiğimiz şekilde fonksiyonların default değerleri olabilir. biz calculateAndPrint2 fonksiyonunda numberOne ve numberTwo’ya istersek default değer verebiliriz. Sondaki fonksiyonumuz da bir parametre sonuçta günün sonunda, istersek biz bu fonksiyon parametresini de opsiyonel hale getirebiliriz.
eşittir deyip bir higher order tanımı yaparak sağlarız bunu

fun calculateAndPrint2(numberOne: Int=3 , numberTwo: Int=5, operation : ( Int , Int ) -> Int= {numberOne,numberTwo->numberOne + numberTwo } ){
val result = operation(numberOne,numberTwo)
println("Result : $result")
}

şimdi default değeri olduğu için main fonksiyonda veya istediğimiz yerde şöyle çağırabiliriz;

calculateAndPrint2(3,45)

Fonksiyonumuz higher order bir fonksiyon olmasına rağmen fonksiyon parametremiz yok artık. Fonksiyon parametresi yoksa;
operation : ( Int , Int ) -> Int= {numberOne,numberTwo->numberOne + numberTwo } bu demek ki opsiyonel, opsiyonelse call edildiği durumda şurası çalışır {numberOne,numberTwo->numberOne + numberTwo }

fun defaultSum(numberOne: Int, numberTwo: Int ){
return numberOne + numberTwo
}
fun calculateAndPrint2(numberOne: Int=3 , numberTwo: Int=5, operation : ( Int , Int ) -> Int= ::defaultSum ){
val result = operation(numberOne,numberTwo)
println("Result : $result")
}

Bir higher order function’a parametre verirken classismi.() şeklinde bir tanımlama yapılabilir, bu tanımlama şekline extension fonksiyonlar denir kotlin’de. Bu sayede ilgili class da parametre parantezi içerisinde yazılabilir.

Önce Sayılar: 105,9'u print eder ardından 45 değeri döner,45 değerini de result değişkenine vermiş oluruz. Yani result’u yazdırmadan önce sayıları da print ettirmiş olduk.

Kotlin’deki Yaygın Higher-Order Fonksiyonlar

  • map Fonksiyonu

map fonksiyonu, bir koleksiyondaki her bir öğeye bir fonksiyon uygulayarak yeni bir koleksiyon oluşturur.

fun main() {
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 }
println("Doubled: $doubled") // Output: Doubled: [2, 4, 6, 8, 10]
}
  • filter Fonksiyonu

filter fonksiyonu, bir koleksiyondaki belirli bir koşulu sağlayan öğeleri seçerek yeni bir koleksiyon oluşturur.

fun main() {
val numbers = listOf(1, 2, 3, 4, 5)
val even = numbers.filter { it % 2 == 0 }
println("Even: $even") // Output: Even: [2, 4]
}
  • reduce Fonksiyonu

reduce fonksiyonu, bir koleksiyonun öğelerini birleştirerek tek bir değer oluşturur.

fun main() {
val numbers = listOf(1, 2, 3, 4, 5)
val sum = numbers.reduce { acc, num -> acc + num }
println("Sum: $sum") // Output: Sum: 15
}

Peki biz bu higher order fonksiyonları nerde kullanırız?

Geriye dönük bir veri tranferi, bir haberleşme yapmak istiyorsak,bir fonksiyonun call edildiği yere bir bilgi taşımak isteniyorsa higher order fonksiyon kullanılır.

--

--