Yüksek Dereceden Fonksiyonlar – Swift – (Higher Order Functions)

 

Yüksek dereceden fonksiyonlar(Higher Order Functions), bir veya birden fazla fonksiyonu parametre olarak alan ve işlem sonunda bir fonksiyon dönen fonksiyonlardır. Bu fonksiyonlar bizim işimizi oldukça kolaylaştırırlar. Kullanmadan da çözebileceğimiz işleri, çok daha kısa yolla çözebilme imkanını bize verirler. Diziler ile çok yaygın olarak kullanıldığını görebiliriz. Bu ders içeriğinde en yaygın olan, en çok karşılaştığımız da diyebiliriz, yüksek dereceden fonksiyonları inceleyeceğiz. Bu fonksiyonlar: forEach, map, compactMap, flatMap, filter, reduce, contains, sorted & sort, split , removeAll. Şimdi sırasıyla fonksiyonları incelemeye başlayalım:

Not: Yüksek dereceden fonksiyonlar bize shorthand argümanları kullanma imkanı verir. Shorthand argüman: örneğin bir dizinin her iterasyonundaki elemanına kısa yoldan erişim diyebiliriz. $0, $1 gibi ifadeler ile temsil edilirler. Aşağıda örnekler ile daha detaylı açıklayacağız.

Yüksek Dereceden Fonksiyonlar

ForEach Fonksiyonu

forEach bahsedeceğimiz yüksek dereceden fonksiyonlardan birincisi. forEach, for-in döngülerine çok benzemekle beraber bir döngü değildir. Döngü olmadığı için de içersinde “break” ve “continue” kullanılamaz. Bir örnek ile bakalım nasıl kullanabileceğimize:

Öncelikle bir for-in döngüsü yazıp bunun forEach karşılığına bakalım:

let countries = ["TR","GB","AL","CAN", "USA","RUS"]
        
// For-in Loop
for country in countries {
    print(country)
}
        
// forEach fonksiyonu
countries.forEach { country in
     print(country)
}
        
// Shorthand argüman ile forEach 
countries.forEach { print($0) }

Aslında yukarıdaki kod parçalarının üçü de aynı işi yapmaktadır ama yüksek dereceden fonksiyonların küçücük bir kod parçasında bile nasıl daha sade olabildiğini bize gösteriyor. Özellikle shorthand argümanlar en büyük avantajlardan bir tanesidir. Yukarıdaki örnekte shorthand argüman her seferinde bir string değere erişiyor ancak bir nesne dizimiz olsaydı eğer, bu nesneye erişecekti ve biz bu nesnelerin parametrelerine “$0” sonuna nokta koyarak erişebiliriz.

Map Fonksiyonu

Belki de en çok bilinen yüksek dereceden fonksiyon map fonksiyonudur. map fonksiyonu, bir collection içindeki bütün elemanlara erişip, bu elemanlar ile istediğimiz işleri yaptıktan sonra, işlem yapılmış collectionı bize veren bir fonksiyondur. Örnek yapımı biraz değiştireceğim: öncelikle bir araba struct’ı oluşturacağım:

    struct Car {
        let name: String
        let model: String
    }

Daha sonra bu araçları atayabileceğim bir araç sahibi sınıfı oluşturacağım ve sadece kullanacağım iki parametreden oluşturacağım. Bu modeller farklı parametreler de alabilir, biz sadece map fonksiyonuna odaklanacağız:

    struct Owner {
        let carName: String
        let carModel: String
    }

Şimdi araçlarımızı tanımlayalım:

let cars: [Car] = [.init(name: "Audi", model: "A3"),
                   .init(name: "BMW", model: "320"),
                   .init(name: "Mercedes", model: "A180")]

Gerekli hazırlıklarımızı yaptık. Şimdi forEach fonksiyonunda yaptığımız gibi sırasıyla neler yapabildiğimize bakalım:

        
var owners = [Owner]()
  
for car in cars {
     owners.append(.init(carName: car.name, carModel: car.model))
}
       
owners = cars.map({ (car) -> Owner in
   .init(carName: car.name, carModel: car.model)
})

owners = cars.map {.init(carName: $0.name, carModel: $0.model)}

Yine aynı şekilde üç kod parçası da aynı işi yapmaktadır.

CompactMap Fonksiyonu

compactMap fonksiyonu map fonksiyonu ile aynı işi yapar, aralarındaki küçük bir fark dışında. Eğer fonksiyonu kullanacağımız collection nil parametreler içeriyorsa, compactMap bu değerleri dönüşteki collection içinden çıkarıyor. Hemen bir örnek ile açıklayalım:

let numbers = [0, nil, 2, 3, 4, nil, 6, nil, 8, 9]

let nullableArray = numbers.map { $0 }
print(nullableArray) // -> [Optional(0), nil, Optional(2), Optional(3), Optional(4), nil, Optional(6), nil, Optional(8), Optional(9)]

let numberArray = numbers.compactMap { $0 }
print(numberArray)  // -> [0, 2, 3, 4, 6, 8, 9]

Yukarıdaki çıktılardan anlayacağımız üzere map fonksiyonu optional değerler ile işlem yaparken, compactMap optional değerleri ortadan kaldırıyor. İhtiyaç durumuna göre her iki fonksiyon da kullanılabilir.

FlatMap Fonksiyonu

Eğer bir collection içinde farklı collectionlar var ise ve biz bunu tek bir collection olarak ifade etmek istersek flatMap fonksiyonu bizim için çok faydalıdır. Örnek ile daha kolay anlaşılacaktır:

let twoDimensionArray = [[1,2,3], [4,5,6], [7, nil], [8,9,0]]
var singleDimension = [Int]()
        
for array in twoDimensionArray {
    for element in array {
        singleDimension.append(element ?? 0)
    }
}

let singleDimensionFlatMap = twoDimensionArray.flatMap { (array) -> [Int?] in
    return array
}
        
let singleDimensionFlatMapShortHand = twoDimensionArray.flatMap { $0 }

FlatMap bize iç içe olan collection tiplerini birleştirme imkanını verir.

Not: Eğer collectionlar içinde nil değer bulunuyorsa flatMap sonucu üretilen yeni collection elemanları optional olur.

Filter Fonksiyonu

filter fonksiyonu da bir collection alarak bu collection içindeki elemanlara bir takım koşullar sunmamıza izin verir. Koşulu sağlayan elemanları yine aynı tipte bir collection olarak döndürür diyebiliriz. Hemen bir örneğe bakalım:

let numberArray = [43, 62, 69, 98, 223, 745, 573, 234, 12, 444]

var resultArray: [Int] = []
        
for number in numberArray {
    if number % 3 == 0 && number % 2 == 0 {
         resultArray.append(number)
    }
}
        
resultArray = numberArray.filter({ (item) -> Bool in
    return item % 3 == 0 && item % 2 == 0
})
        
resultArray = numberArray.filter { $0 % 3 == 0 && $0 % 2 == 0 }

Evet yukarıda fonksiyonun nasıl çalıştığını öğrendik. Bu klasik örnek dışında geliştirme sırasında çok işimize yarayacak bir örnek vermek isterim. Farz edelim bir kullanıcı listemiz var ve sayfanın en üstünde bir searchBar vasıtasıyla bu liste içinde arama yapıp listeleme yapmamız isteniyor. Hatta farz edelim sadece bu kullanıcıların ismi ile değil hem ismi hem de telefon numaraları ile arama yapalım. Daha sonra tableview reload işlemi veya ui yapınız nasılsa halledilebilir. Bizim odak noktamız filter fonksiyonu. Bu işlemi filter fonksiyonu ile çok rahat yapabiliriz. Önce user modelimi oluşturacağım:

    struct User {
        let name: String
        let phoneNumber: String
    }
    
    let userList: [User] = [.init(name: "Ali", phoneNumber: "123123123"),
                            .init(name: "Veli", phoneNumber: "23423423"),
                            .init(name: "Mehmet", phoneNumber: "64362141234"),
                            .init(name: "Orhun", phoneNumber: "0843989303"),
                            .init(name: "Altan", phoneNumber: "08002342323"),
                            .init(name: "Enes", phoneNumber: "7568588488"),
                            .init(name: "Şerafettin", phoneNumber: "000001212"),]
    
    
    func getFilteredList(filterText: String) -> [User] {
        let filteredUserList = userList.filter {
            return $0.name.range(of: filterText,
                                           options: [ .caseInsensitive,
                                                      .diacriticInsensitive ]) != nil ||
                $0.phoneNumber.range(of: filterText,
                                                 options: [ .caseInsensitive,
                                                            .diacriticInsensitive ]) != nil
        }
        return filteredUserList
    }

Yukarıdaki metot istediğimiz arama sonucunu bize dönecektir. “0” filterText değeri için (Orhun, Altan ve Şerafettin) ve “Al” filterText değeri için (Ali ve Altan) isimli nesneleri döndürdüğünü test ettim.

Reduce Fonksiyonu

Bu fonksiyonu collection içindeki tüm elemanları tek bir değişken ya da sabitte toplamak için kullanırız. En yaygın kullanımını dizi içindeki tüm elemanların toplamı olarak görebiliriz. Buna örnek vermek gerekirse:

let numbers = [43, 62, 69, 98, 223]
let sum = numbers.reduce(0, {$0 + $1})
print("sonuç: \(sum)") // sonuç: 495

Reduce fonksiyonunda iki adet shorthand argüman kullandık. Şimdiye kadarki gördüğümüz fonksiyonlarda hep tek argüman vardı ve bu da her iterasyondaki elemanı bize veriyordu. Buradaki argümanları dökümandan faydalanarak şu şekilde açıklayabiliriz:

func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Int) throws -> Result) rethrows -> Result

Yukarıdaki fonksiyonun parametrelerinden initialResult, bizim ilk değerimiz. Yukarıda yazdığımız sum sabitini hesaplarken initialResult olarak 0 belirledik. Fonksiyon daha sonra gelecek değerleri bu parametreye ekleyerek devam ediyor. Yani initialResult’ı 5 belirlemiş olsaydık sonucumuz 500 olarak hesaplanacak idi. nextPartialResult parametresi iki adet değeri tutuyor. Birincisi Result parametresi; bu parametre fonksiyonun anlık durumunu gösterir. Yani ilk iterasyona girerken bu parametre initialResult parametresine eşittir. İlk iterasyon bittiğinde bu parametrenin, örnek üzerinden devam edersek, 43 olduğunu görürüz. İkinci iterasyon sonunda 105 olduğunu. Kısacası bu parametre her iterasyon sonunda toplamın kaç olduğunu tutar. Int ile temsil edilen ikinci değer ise her iterasyondaki eklenecek değeri temsil eder. Birinci iterasyonda 43, ikinci iterasyonda 62 gibi… Yukarıdaki fonksiyondaki nextPartialResult içinde, $0 Result’ı, $1 ise Int değeri temsil etmiştir. Aynı işlemler String için de geçerlidir. Eğer bir string dizimiz var ise nextPartialResult içindeki ikinci parametremiz String kabul edecektir.

Reduce fonksiyonu stringleri birbirine eklemek için de kullanılabilir. Buna örnek vermek gerekirse:

let characters = ["A","l","i"]
let name = characters.reduce("", {$0 + $1})
print("Benim adım \(name)") // Benim adım Ali

Bonus: Fonksiyonu daha da kısaltabiliriz. Örneğin toplama işlemini şu şekilde yapabiliriz:

let sum = numbers.reduce(5, +)
print("sonuç: \(sum)") // sonuç: 500

Buradaki “+” işareti bütün elemanları toplayıp beşe ekle anlamına gelmektedir. Aynı işlemi çarpma için de yapabiliriz. Bu defa “*” kullanmalıyız 🙂

Contains Fonksiyonu

Bu fonksiyon bir collectionın, aradığımız herhangi bir elemanı içerip içermediğini kontrol eder. Örnek ile açılarsak daha iyi olacaktır. Farz edelim sepetimiz var ve sepetimizde bir takım ürünler var. Bir ürünün sepetimizde olup olmadığını unuttuk ve ürün sepette ise tekrar almamıza gerek yok. Bu işlemi gerçekleştirelim o halde:

Önce bir sepeti oluşturup içine biraz eleman ekleyelim:

 struct BasketItem {
        let itemId: Int
        let itemName: String
    }
    
    var basket: [BasketItem] = [.init(itemId: 1342, itemName: "Yağ"),
                                .init(itemId: 2632, itemName: "Zeytin"),
                                .init(itemId: 6453, itemName: "Yufka"),
                                .init(itemId: 1323, itemName: "Şeker")]
    
    let item = BasketItem(itemId: 2632, itemName: "Zeytin")
    let newItem = BasketItem(itemId: 5959, itemName: "Peynir")
    
    basket.contains {$0.itemId == item.itemId} ? print("Bu ürün zaten sepetinizde"): basket.append(item)
    print("sepetiniz -> \(basket)")
    basket.contains {$0.itemId == newItem.itemId} ? print("Bu ürün zaten sepetinizde"): basket.append(newItem)
    print("sepetiniz -> \(basket)")

   //  Bu ürün zaten sepetinizde

    //  sepetiniz ->

    //  BasketItem(itemId: 1342, itemName: “Yağ”),

    //  BasketItem(itemId: 2632, itemName: “Zeytin”),

    //  BasketItem(itemId: 6453, itemName: “Yufka”),

    //  BasketItem(itemId: 1323, itemName: “Şeker”)]

    

    //  sepetiniz ->

    //  BasketItem(itemId: 1342, itemName: “Yağ”),

    //  BasketItem(itemId: 2632, itemName: “Zeytin”),

    //  BasketItem(itemId: 6453, itemName: “Yufka”),

    //  BasketItem(itemId: 1323, itemName: “Şeker”),

    //  BasketItem(itemId: 5959, itemName: “Peynir”)]

Konsol çıktıları yukarıdaki gibi olacaktır. Fonksiyon ilk ürünü id vasıtasıyla kontrol ettiğinde ürünün sepette olduğunu görüyor ve sepette olduğu için true dönüyor ve sepete eklemeden istediğimiz çıktıyı veriyor. İkinci ürün ise tam tersine sepette olmadığını görüp false dönüyor ve ürün sepete ekleniyor.

Sort & Sorted Fonksiyonları

Her iki fonksiyon da verilen collection içerisindeki elemanları sıralamak için kullanılır. Ancak aralarında bir fark vardır. Farz edelim bir dizimiz var. Sort fonksiyonu elimizdeki bu diziyi kendi içerisinde sıralarken, sorted fonksiyonu dizinin elemanlarının sıralı olduğu yeni bir dizi oluşturur. Dolayısıyla sorted fonksiyonu mevcut dizimizde bir değişiklik yapmaz. Örnekeler ile bunu gösterelim:

var numbers1 = [143, 623, 469, 298, 223]
var numbers2 = [143, 623, 469, 298, 223]
    
    
numbers1.sort()
print(numbers1) // [143, 223, 298, 469, 623]

let result = numbers2.sorted()
print(result) // [143, 223, 298, 469, 623]

Farkları yukarıdaki gibi görülebilir. İhtiyacımıza göre ikisinden birini seçebiliriz. Yukarıdaki örneklerde fonksiyonun bize sunduğu argümanları kullanmadık. Dolayısıyla default olan küçükten büyüğe sıralandı. Peki büyükten küçüğe nasıl sıralayabiliriz:

numbers1.sort { (num1, num2) -> Bool in
    num1 > num2
}
print(numbers1) // [623, 469, 298, 223, 143]

Aynı işlemi shorthand argümanlar ile de yapabiliriz:

numbers1.sort { $0 > $1 }
print(numbers1) // [623, 469, 298, 223, 143]

Peki bu işi bir ileri boyuta taşıyabilir miyiz?

numbers1.sort(by: > )
print(numbers1) // [623, 469, 298, 223, 143]

Evet sıralama işini bu kadar kısa halledebiliriz. Büyükten küçüğe sıraladığımız son üç kod parçasında sadece sort fonksiyonunu kullandık ancak aynı işlemleri sorted fonksiyonu ile de yapabiliriz. En başta da bahsettiğimiz gibi sorted fonksiyonu bize yeni bir dizi dönecektir ve bu diziyi bir değişkende ya da sabitte tutmamız gerekmektedir.

Split Fonksiyonu

Bu fonksiyonu bir örnek üzerinden açıklamak istiyorum. Örneğin bir cümlede kaç tane kelime ve bağlaç olduğunu bulmak istiyorsunuz. Böyle bir durumda cümleyi boşluklarından ayırdığımızda istediğimiz sonuca erişebiliriz. Peki bunu split fonksiyonu ile yapalım o halde:

let splittable = "Bu cümlede beş kelime var."

let array = splittable.split(separator: " ")
print(array) // ["Bu", "cümlede", "beş", "kelime", "var."]
print(array.count) // 5

Bir stringi bu şekilde parçalayıp array olarak aldık ve kelime sayımızı da bulmuş olduk. Peki başka neler yapabiliriz:

let array = splittable.split { (character) -> Bool in
    return character == "e"
}
// Bu şekilde string içindeki bütün harflere erişebiliriz. true değerini döndürdüğümüz andaki elemanı array içine ekleyecektir.
print(array) // ["Bu cüml", "d", " b", "ş k", "lim", " var."]

Bu fonksiyonda maksimum ayırma sayısı belirleyebiliriz. Yani bir kere ayırma işlemini yaptıktan sonra dur diyebiliriz. Şöyle ki:

let array = splittable.split(maxSplits: 1, whereSeparator: { $0 == " " })
print(array) // ["Bu", "cümlede beş kelime var."]

RemoveAll Fonksiyonu

Bu fonksiyon adından da anlaşılacağı bir collection içindeki elemanların hepsini veya istediğimiz bir koşulu sağlayan bütün elemanları siler. Örnek ile devam edelim:

var numbers = [143, 623, 469, 298, 223]
numbers.removeAll()
print(numbers) // []

Tabi istediğimiz bir koşulu da verebiliriz. Şöyle diyelim; 300 altındaki tüm sayıları sil:

numbers.removeAll { $0 < 300 }
print(numbers) // [623, 469]

Fonksiyon birleştirme (Function Concatenation)

Yüksek dereceden fonksiyonları ard arda kullanabiliriz. örneğin bir dizinin elemanlarını filtrelerken bu filtrelediğimiz elemanların bir propertysini toplayabiliriz. Şöyle bir örnek verelim:

    struct Item {
        let isSelected: Bool
        let count: Int
    }
    
    let items: [Item] = [.init(isSelected: false, count: 3),
                         .init(isSelected: true, count: 5),
                         .init(isSelected: false, count: 2),
                         .init(isSelected: true, count: 9)]

Yukarıdaki dizideki elemanlardan isSelected parametresi true olanların count değerlerini toplamak istesek işlemi nasıl yaparız:

let total = items
            .filter { $0.isSelected }
            .reduce(0, {$0 + $1.count})
        
print(total) // 14

Yüksek dereceden fonksiyonlar ile bu kadar kolay yapılabilecek bir işlem. Farklı kombinasyonlar oluşturuşabilir 🙂

Özet

Bu desimizin içeriğinde yüksek dereceden fonksiyonlara değindik. Elimden geldiğince açıklamaya çalıştım. Bu fonksiyonların ne kadar işlevsel olduklarını verdiğimiz küçük örneklerden dahi görebiliriz. Özellikle shorthand argümanlar bu fonksiyonları bizim için kullanışlı yapmaktadır. Kod içinde çok sık ihtiyacımız olan bu işlemler bu fonksiyonlar ile kolayca çözülebilmektedir. Dersi burada bitirirken herkese faydalı olmasını umarak, mutluluklar diliyorum. Soru, görüş ve önerilerinizi yorum kısmından veya soru-cevap kısmından iletebilirsiniz. Sağlıcakla…

42

Ali Hasanoglu

Yorum Yaz

Haftalık Bülten

Mobilhanem'de yayınlanan dersleri haftalık mail almak ister misiniz?