İçeriğe geç
academia.sh

Ders 12 / 21

Numaralandırmalar

Ham değerin ve ilişkili değerin ne zaman kurulduğunun, desen eşlemeyle nasıl açıldığının ve hesaplanan özelliğin her okunuşta koştuğunun ölçümü.

Yayın Güncelleme

Bu derste

  • Ham değerli numaralandırmanın rawValue ve rawValue ile kurma yollarını ölçer
  • İlişkili değerin durum kurulurken bir kez değerlendirildiğini gösterir
  • switch ile desen eşlemenin ilişkili değeri bir ada bağladığını çıktıdan okur
  • Numaralandırmadaki hesaplanan özelliğin her okunuşta yeniden koştuğunu ölçer
İçindekiler

Önceki ders alan listesi olan tipleri ölçtü: yapı ve sınıf, hep aynı alanları taşır. Bazı veriler ise bir alan listesi değil, birkaç ayrı durumdan birinde olmayı gerektirir. Haftanın günü yedi değerden biridir; bir ölçüm ya derece cinsinden bir sayıdır ya da bir etikettir. Bu iki örnek farklı şeyleri anlatır, ama ikisi de numaralandırma (enumeration) ile ifade edilir.

Swift numaralandırması birçok dildeki karşılığından daha güçlüdür. Her durum (case) yalnızca bir ad değildir; tanımına göre bir ham değer (raw value) ya da durumla birlikte taşınan bir ilişkili değer (associated value) olabilir. Numaralandırmalar ayrıca hesaplanan özellik ve yöntem taşıyabilir; saklanan örnek özelliği taşıyamaz. Ders önce bu iki değer çeşidinin ne zaman kurulduğunu, sonra bir numaralandırma içindeki hesaplamanın ne zaman koştuğunu ölçer. Kursun ölçü ekseni değişmez: bu ifade ne zaman değerlendiriliyor? Dört an vardır: hemen, ilk kullanımda, her kullanımda, hiç. Probe sınıfı aşağıda olduğu gibi verilir.

Çekirdek

Her ders aynı çekirdeği kullanır. Probe bir ifadenin kaç kez değerlendirildiğini (eval) ve kaç kez kullanıldığını (use) sayar; moment özelliği bu iki sayıdan anı çıkarır. Açılış dersindeki bağlayıcı ölçümde sekiz ifadenin dağılımı 2/1/3/2 idi: ikisi hemen, biri ilk kullanımda, üçü her kullanımda, ikisi hiç. Bu derste yalnız kendi kesitini ölçeriz.

// Probe.swift — bir ifadenin ne zaman degerlendirildigini kaydeder
final class Probe {
    let label: String
    private(set) var evaluations = 0
    private(set) var uses = 0
    private var evaluatedBeforeUse = false

    init(_ label: String) {
        self.label = label
    }

    func eval<T>(_ value: T) -> T {
        evaluations += 1
        if uses == 0 { evaluatedBeforeUse = true }
        return value
    }

    func use<T>(_ access: () -> T) -> T {
        uses += 1
        return access()
    }

    var moment: String {
        if evaluations == 0 { return "hic" }
        if evaluations > 1 { return "her-kullanimda" }
        return evaluatedBeforeUse ? "hemen" : "ilk-kullanimda"
    }

    func report() {
        print("\(label): \(evaluations) degerlendirme, \(uses) kullanim -> \(moment)")
    }
}

func tally(_ probes: [Probe]) {
    for moment in ["hemen", "ilk-kullanimda", "her-kullanimda", "hic"] {
        let count = probes.filter { $0.moment == moment }.count
        print("\(moment): \(count)")
    }
}

Ölçümlerde iki numaralandırma kullanılır. Weekday bir Int ham değerine sahiptir ve CaseIterable ile bütün durumlarını listeler; ilk duruma 1 verildiğinde sonrakiler 2 ve 3 olur. Reading ise durumlarıyla birlikte değer taşır: celsius bir Double, label bir String alır. Bu bloğun çıktısı yoktur, çünkü yalnızca tipleri tanımlar.

// Types.swift — ham degerli ve iliskili degerli numaralandirmalar
enum Weekday: Int, CaseIterable {
    case monday = 1, tuesday, wednesday
}

enum Reading {
    case celsius(Double)
    case label(String)
}

Ham Değer

Ham değer, bir durumun derleme zamanında sabit olarak atanmış karşılığıdır. Her durumun ham değeri farklı olmalıdır ve değerler tanımda yazılır. İlk durum için 1 yazıldığında sonraki durumların değeri bir artarak belirlenir. Ham değere rawValue özelliği ile ulaşılır ve ters yönde, ham değerden duruma gitmek için Weekday(rawValue: 3) biçimi kullanılır. Ters yönün her ham değer için bir durum bulamayacağı açıktır; bu yüzden bu biçim bir isteğe bağlı değer döndürür.

// main.swift — ham deger, rawValue ile kurma ve allCases
print(Weekday.tuesday.rawValue)
print(Weekday(rawValue: 3) == .wednesday)
print(Weekday(rawValue: 9) == nil)
print(Weekday.allCases.count)
2
true
true
3

Birinci satır tuesday durumunun ham değerini verir: ilk durum 1 ile başladığı için 2’dir. İkinci satır rawValue: 3 ile kurulan değerin .wednesday ile eşit olduğunu gösterir. Üçüncü satır, hiçbir durumun ham değeri olmayan 9 için sonucun nil olduğunu gösterir; ilk derste gördüğümüz boş kutu burada bir Int dönüşümünün değil, bir durum aramanın sonucudur. Dördüncü satır CaseIterable’ın eklediği allCases ile üç durumu saydığını gösterir.

Ham değer burada bir kimlik ve dönüşüm aracıdır. Bir ağ yanıtından, bir dosyadan ya da bir veritabanı alanından gelen tamsayıyı güvenli biçimde bir duruma çevirmek için kullanılır ve geçersiz değerin ne yapılacağı konusunda çağıranı karar vermeye zorlar.

Ham değer yalnız tamsayı olmak zorunda değildir. Tipi String olan bir numaralandırmada her durumun ham değeri, açıkça yazılmadıysa durumun kendi adıdır. Aynı üç işlemi bir gezegen listesiyle yapalım: ham değeri okumak, ham değerden durum kurmak ve geçersiz bir ham değerle kurmayı denemek.

// main.swift — String ham deger belirtilmezse durum adidir
enum Planet: String {
    case mercury, venus, earth
}

print(Planet.venus.rawValue)
print(Planet(rawValue: "earth") == .earth)
print(Planet(rawValue: "pluto") == nil)
venus
true
true

Birinci satır venus durumunun ham değerinin, hiçbir değer yazılmadığı hâlde "venus" olduğunu gösterir. İkinci satır "earth" ham değerinin .earth durumunu verdiğini, üçüncü satır ise listede olmayan "pluto" için sonucun nil olduğunu gösterir. Tamsayı örneğindeki ters yön burada da aynı biçimde isteğe bağlı bir değer döndürdü: ham değerden duruma giden yol her zaman bir yokluk olasılığı taşır.

TS14. Bu ölçümler Int ve String ham değerli birer numaralandırma için. Diğer ham değer tipleri (Double, Character) ve ham değeri yalnızca bir kısmı için yazılmış Int durumlarının davranışı ayrıca ölçülmedi.

İlişkili Değer ve Desen Eşleme

İlişkili değer ham değerden farklı çalışır. Ham değer tanım sırasında sabittir ve aynı durumun her örneğinde aynıdır. İlişkili değer ise durum kurulurken belirlenir ve her örnek kendi değerini taşır. Reading.celsius(21.5) ile Reading.celsius(30.0) aynı durumdur, ama farklı veri taşır. Böyle bir değerin içindekine erişmek için switch ile desen eşleme (pattern matching) kullanılır: her durumun kolunda ilişkili değer bir adla bağlanır.

// main.swift — iliskili degerin desen eslemeyle acilmasi
let readings = [Reading.celsius(21.5), Reading.label("sicak")]
for reading in readings {
    switch reading {
    case .celsius(let degrees):
        print("derece", degrees)
    case .label(let text):
        print("etiket", text)
    }
}
derece 21.5
etiket sicak

Dizideki iki Reading değeri sırayla switch’e girer. İlki celsius kolunu seçer ve içindeki 21.5’i degrees adına bağlar; ikincisi label kolunu seçer ve "sicak" dizesini text adına bağlar. Her kolda bağlanan ad yalnızca o kolun gövdesinde geçerlidir ve tipi o durumun ilişkili değerinin tipidir. Derleyici bütün durumların ele alındığını denetler: bir durum atlanırsa switch tamamlanmamış sayılır ve eksik durum için bir kol yazmak ya da default kullanmak gerekir.

Bu davranış, ilk dersteki isteğe bağlı değer ile aynı kalıptır. Optional de bir numaralandırmadır ve .some durumu bir ilişkili değer taşır; if let ve switch içindeki case .some(let value) aynı eşlemenin iki yazılışıdır. Bu ders, o dersteki kutunun neden böyle davrandığını gösteren ikinci bir örnek verir.

Tek bir duruma bakmak gerektiğinde switch yazmak ağır kaçar. if case aynı eşlemeyi tek bir koşul olarak yazar: durum eşleşirse ilişkili değer bağlanır ve blok koşar, eşleşmezse else kolu koşar. Virgülle bir koşul daha eklenebilir; ilişkili değer bağlandıktan sonra o koşulda kullanılır.

// main.swift — if case ile tek bir duruma bakmak
let current = Reading.celsius(18.0)
if case .celsius(let degrees) = current, degrees < 20 {
    print("serin", degrees)
}
if case .label(let text) = current {
    print("etiket", text)
} else {
    print("etiket degil")
}
serin 18.0
etiket degil

Birinci if case, current değerini celsius durumuyla eşleştirdi, 18.0’ı degrees adına bağladı ve virgülden sonraki degrees < 20 koşulunu da geçti. İkinci if case aynı değeri label durumuyla eşleştirmeye çalıştı, eşleşme olmadığı için else kolu koşturdu. Bu, switch ile aynı davranıştır: eşleşmeyen durumda ilişkili değer hiç bağlanmaz ve ilgili ad kapsamda yoktur.

TS15. Bu ölçüm iki durumlu ve her durumunda tek ilişkili değer olan bir numaralandırma için. Birden çok ilişkili değer taşıyan durumlar, indirect numaralandırmalar ve where koşullu kollar ölçülmedi.

İlişkili Değer Ne Zaman Kurulur

İlişkili değer, durum kurulurken verilen bir ifadedir. Reading.celsius(stamp.eval(19.0)) yazıldığında stamp.eval(19.0) ifadesi durum kurulurken bir kez değerlendirilir ve sonuç durumun içinde saklanır. Sonradan switch ile okunduğunda ifade yeniden hesaplanmaz; okunan şey durumun içindeki saklı değerdir. İki kez okuyarak bunu ölçelim.

// main.swift — iliskili deger case kurulurken bir kez degerlendirilir
let stamp = Probe("iliskili-deger")
let reading = Reading.celsius(stamp.eval(19.0))

for _ in 1...2 {
    _ = stamp.use { () -> Double in
        switch reading {
        case .celsius(let degrees):
            return degrees
        case .label:
            return -1
        }
    }
}
stamp.report()
iliskili-deger: 1 degerlendirme, 2 kullanim -> hemen

Bir değerlendirme, iki kullanım ve an hemen. Değerlendirme, reading kurulduğu satırda gerçekleşti, use çağrılarından önce. Her iki switch aynı saklı değeri okudu ve ifadeyi yeniden koşturmadı. Bu, ham değerden farklıdır: ham değerin ifadesi hiç koşmaz, çünkü o bir sabittir. İlişkili değerin ifadesi ise örnek başına bir kez koşar.

Ölçümün gösterdiği pratik sonuç şudur: ilişkili değere yazılan pahalı bir hesaplama, durum kurulurken ödenir ve okuyuşlar ek bir hesap gerektirmez. Hesabın okunduğu anda yapılmasını isteyen kod, ilişkili değeri değil bir hesaplanan özelliği kullanmalıdır. Bir sonraki bölüm bu özelliği ölçer.

TS16. Bu ölçüm sabit bir Double literalini sarmalayan bir ifade için. İlişkili değerin bir işlev çağrısı ya da başka bir ilişkili değer içeren ifade olduğu durumlar ayrıca ölçülmedi.

Sınırlayıcı Ölçüm

Ham değer ve ilişkili değer durum kurulurken ya da tanımda sabitlenir; yani okuyuş maliyetsizdir. Numaralandırma ise saklanan değer dışında bir de hesaplanan özellik (computed property) taşıyabilir. Bu özellik bir değer saklamaz, her okunduğunda gövdesini çalıştırır. Sensor numaralandırmasında fahrenheit özelliği, ilişkili değer olarak taşınan Celsius derecesini her okunuşta Fahrenheit’a çevirir. Çevirme ifadesi bir Probe ile işaretlidir ve özellik iki kez okunur.

// main.swift — numaralandirmanin hesaplanan ozelligi her okunusta kosar
enum Sensor {
    case reading(Probe, Double)

    var fahrenheit: Double {
        switch self {
        case .reading(let probe, let celsius):
            return probe.eval(celsius * 9 / 5 + 32)
        }
    }
}

let derived = Probe("hesaplanan-ozellik-sensor")
let sensor = Sensor.reading(derived, 20)
_ = derived.use { sensor.fahrenheit }
_ = derived.use { sensor.fahrenheit }
derived.report()
print(sensor.fahrenheit)
hesaplanan-ozellik-sensor: 2 degerlendirme, 2 kullanim -> her-kullanimda
68.0

İki okuyuş, iki değerlendirme, an her kullanımda. Hesaplanan özellik sonucu saklamaz; sensor.fahrenheit her yazıldığında gövdesi baştan koşar. İlişkili değer 20 ise kurulurken sabitlenmişti, ama ondan türeyen 68.0 her okunuşta yeniden hesaplanır. Çıktının ikinci satırı, aynı özelliğin bir sonraki okunuşunda aynı sonucu verdiğini gösterir.

Bu sonuç dersin kuralını daraltır. “İlişkili değer durum kurulurken bir kez değerlendirilir” cümlesi yalnızca ilişkili değerin kendisi için doğrudur. İlişkili değerden türeyen her hesaplanan özellik, her okunuşta baştan çalışır. Aynı hesabın birden çok yerde gerektiği durumda sonucu bir adda tutmak, hesaplanan özelliği her yerde yeniden okumaktan ucuzdur.

Ham değer için de bir sınır vardır ve o ölçülemez, çünkü derlenmez. Ham değer, kaynakta yazılan bir sabit olmak zorundadır. Aşağıdaki parça bir işlevin dönüşünü ham değer olarak atamaya çalışır ve derlenmez: derleyici onu reddeder. Hata iletisi, ham değerin bir sabit yazım olması gerektiğini bildirdi. Parça burada yalnızca anlatım için durur.

func rawOf() -> Int { 3 }
enum Bad: Int {
    case a = 1
    case b = rawOf()   // derlenmez: ham deger bir sabit olmali
}

Bu sınır iki değer çeşidinin bir ikinci farkını gösterir. İlişkili değer çalışma zamanında hesaplanan herhangi bir ifade olabilir; ham değer ise derleme zamanında bilinen bir sabittir. Ham değerin ifadesi bu yüzden hiçbir zaman çalışma zamanında değerlendirilmez.

TS17. Bu ölçüm iki saklı değer üzerinden türeyen tek bir hesaplanan özellik için. Salt okunur olmayan hesaplanan özellik (set içeren) ve numaralandırmanın yöntemleri ayrıca ölçülmedi. Reddedilen ham değer parçasında yalnızca işlev çağrısı denendi; başka ifade biçimleri denenmedi.

Üç Değerin Yan Yana Okunuşu

Derste üç ayrı değer çeşidi ölçüldü ve her biri başka bir ana düştü. Ham değer tanımda yazılan bir sabittir; ifade olarak hiçbir zaman çalışma zamanında koşmaz, bu yüzden bir ölçüm probu ona bağlanamaz. İlişkili değer, durum kurulurken verilen bir ifadedir ve örnek başına bir kez değerlendirilir; ölçümde bu an hemen çıktı. Hesaplanan özellik ise bir değer saklamaz, her okunuşta gövdesini koşturur; ölçümde bu an her kullanımda çıktı.

Bu üç çeşit birbirinin yerine kullanılabilir gibi görünse de ayrı sorulara yanıt verir. Dışarıdan gelen bir kodu bir duruma çevirmek gerekiyorsa ham değer uygundur, çünkü ters yön bir isteğe bağlı değer döndürür ve geçersiz girdiyi görünür kılar. Durumun yanında örneğe özgü veri taşımak gerekiyorsa ilişkili değer uygundur, çünkü bir durumun farklı örnekleri farklı veri taşıyabilir. Taşınan veriden türeyen bir sonucu gerektiği anda üretmek gerekiyorsa hesaplanan özellik uygundur, ama her okunuşta ödenen bedeli de hesaba katmak gerekir.

Özet

  • Ham değer tanımda sabitlenir ve Weekday(rawValue:) bir isteğe bağlı değer döndürür; bulunamayan değer nil verir.
  • switch ile desen eşleme, ilişkili değeri her kolda bir adla bağlar ve bütün durumların ele alınmasını denetler.
  • İlişkili değer durum kurulurken bir kez değerlendirilir; okuyuşlar ifadeyi yeniden koşturmaz.
  • Numaralandırmadaki hesaplanan özellik sonucu saklamaz ve her okunuşta yeniden koşar.
  • Ham değer derleme zamanında bilinen bir sabit olmak zorundadır; işlev çağrısı ham değer olamaz.
  • Bu dersin anı: hemen. İlişkili değerin ifadesi durum kurulduğu satırda değerlendirilir.

Sonraki Adım

Bu derste bir numaralandırmadaki özelliğin her okunuşta koştuğunu gördük. Özellikler yalnız numaralandırmaya ait değildir: yapılar ve sınıflar da saklanan, hesaplanan ve gözlemleyen özellik taşır, ayrıca bir ifadenin ilk okunuşa ertelenmesini sağlayan lazy vardır. Bir sonraki ders bu özellik çeşitlerini ve her birinin değerini ne zaman kurduğunu ölçer.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat