İçeriğe geç
academia.sh

Ders 01 / 21

Swift'in Tasarım Hedefleri

Swift'in üç hedefini — güvenlik, ifade gücü, başarım — bir ölçü eksenine bağlar: bu ifade ne zaman değerlendiriliyor? Sekiz ifadeyi dört ana ayırır.

Yayın Güncelleme

Bu derste

  • Swift'in güvenlik, ifade gücü ve başarım hedeflerini somut davranışlarla ilişkilendirmek
  • Bir ifadenin değerlendirildiği anı hemen, ilk kullanımda, her kullanımda ve hiç olarak sınıflamak
  • Ölçüm düzeneğindeki `Probe` sınıfının kuralını ve sınırını açıklamak
İçindekiler

Önceki kurslarda bir programın ne yaptığını sordun. Bu kurs başka bir şey soruyor: bir ifade yazdığında, o ifade programın hangi anında çalışıyor? Derleme ile koşum arasındaki ayrımı önceki bir kursta (M11/K01) gördün; burada koşum içindeki sırayı ölçeceğiz. Swift iOS müfredatının dilidir, ve onu üç hedefle tanıtmak gelenektir.

Üç Hedef

Güvenlik: dil, hatayı koşumdan önce yakalamaya çalışır. Bir sabit atanmadan okunamaz; bir değer, tipi isteğe bağlı değer (optional) olmadıkça nil olamaz. İki durumda da derleyici programı reddeder. Bu parçalar Swift 6.4’te derlenmez; derleyici ikisini de reddeder, bu yüzden çıktısız kalırlar.

let total: Int
print(total)
var name: String = nil

İfade gücü: tipi yazmak zorunda kalmazsın, derleyici sağ taraftan çıkarır (tip çıkarımı, type inference). Başarım hedefi ise bu derste ölçülmez; süre ölçmüyoruz, yalnız sıra ölçüyoruz.

Ölçü Ekseni: Dört An

Kurs boyunca tek bir soru soracağız: bu ifade ne zaman değerlendiriliyor? Dört cevap var: hemen, ilk kullanımda, her kullanımda ve hiç. Ölçüm düzeneği iki parçadan oluşur. Önce yardımcı dosya; ders boyunca aynı kalır ve ilk Swift bloğu olarak hep burada durur.

// 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)")
    }
}

eval ifadeyi sarar ve değerlendirildiğini sayar; use ifadeye erişimi sarar. Kural basit: hiç değerlendirilmediyse hic; birden çok değerlendirildiyse her-kullanimda; bir kez değerlendirildi ve bu ilk kullanımdan önceyse hemen; değilse ilk-kullanimda.

DT1. Her gözlem, hic dışında, en az iki kullanım taşır; tek kullanım, tek değerlendirmeyi hemen ile ilk-kullanimda arasında ayırt edemez.

Sekiz ifade, sekiz Probe. Sabit, tembel özellik, hesaplanan özellik, fonksiyon, kısa devre ve kapanış aynı dosyada yan yana durur; kurs bunları sırayla ayrı derslere bölecek.

// main.swift — sekiz ifade, dort an
final class Basket {
    let lazyProbe: Probe
    let computedProbe: Probe
    var items = [3, 4, 5]

    init(lazyProbe: Probe, computedProbe: Probe) {
        self.lazyProbe = lazyProbe
        self.computedProbe = computedProbe
    }

    lazy var total: Int = lazyProbe.eval(items.reduce(0, +))
    var count: Int { computedProbe.eval(items.count) }
}

func square(_ x: Int, probe: Probe) -> Int { probe.eval(x * x) }

let constant = Probe("let-sabiti")
let lazyTotal = Probe("lazy-ozellik")
let computed = Probe("hesaplanan-ozellik")
let fallback = Probe("bos-birlestirme-sagi")
let call = Probe("fonksiyon-govdesi")
let shortCircuit = Probe("ve-isleci-sagi")
let captured = Probe("yakalama-listesi")
let body = Probe("kapanis-govdesi")

let limit = constant.eval(10)
_ = constant.use { limit }
_ = constant.use { limit }

let basket = Basket(lazyProbe: lazyTotal, computedProbe: computed)
_ = lazyTotal.use { basket.total }
_ = lazyTotal.use { basket.total }
_ = computed.use { basket.count }
_ = computed.use { basket.count }

let stored: Int? = 7
_ = fallback.use { stored ?? fallback.eval(0) }
_ = fallback.use { stored ?? fallback.eval(0) }

_ = call.use { square(3, probe: call) }
_ = call.use { square(4, probe: call) }

let ready = false
_ = shortCircuit.use { ready && shortCircuit.eval(true) }
_ = shortCircuit.use { ready && shortCircuit.eval(true) }

let step = 2
let addStep = { [step = captured.eval(step)] (x: Int) -> Int in x + step }
_ = captured.use { addStep(1) }
_ = captured.use { addStep(2) }

let double = { (x: Int) -> Int in body.eval(x * 2) }
_ = body.use { double(3) }
_ = body.use { double(4) }

let probes = [constant, lazyTotal, computed, fallback, call, shortCircuit, captured, body]
for probe in probes { probe.report() }
tally(probes)
let-sabiti: 1 degerlendirme, 2 kullanim -> hemen
lazy-ozellik: 1 degerlendirme, 2 kullanim -> ilk-kullanimda
hesaplanan-ozellik: 2 degerlendirme, 2 kullanim -> her-kullanimda
bos-birlestirme-sagi: 0 degerlendirme, 2 kullanim -> hic
fonksiyon-govdesi: 2 degerlendirme, 2 kullanim -> her-kullanimda
ve-isleci-sagi: 0 degerlendirme, 2 kullanim -> hic
yakalama-listesi: 1 degerlendirme, 2 kullanim -> hemen
kapanis-govdesi: 2 degerlendirme, 2 kullanim -> her-kullanimda
hemen: 2
ilk-kullanimda: 1
her-kullanimda: 3
hic: 2

Sekiz ifade dört ana bölündü: iki hemen, bir ilk-kullanimda, üç her-kullanimda, iki hic. Bu sayı kursun bağlayıcı dökümüdür; sonraki dersler her ifadeyi kendi dersinde açar. let sabiti tanımlandığı satırda değerlendirilir. lazy özellik ilk okunduğunda kurulur ve saklanır; hesaplanan özellik ise her okumada yeniden hesaplanır. Fonksiyon gövdesi her çağrıda koşar; ?? işlecinin sağı sol dolu olduğu için, && işlecinin sağı sol yanlış olduğu için hiç çalışmaz. Yakalama listesi kapanış oluşturulurken bir kez değerlendirilir; kapanışın gövdesi ise her çağrıda.

DT2. Sabit ve yakalama listesi aynı ana düşer, ama yollar farklıdır; bu derste yalnız anı ölçüyoruz, mekanizmayı değil.

Değer Kopyası Yeniden Hesaplamaz

Swift’in güvenlik hedefinin bir ayağı değer semantiğidir (value semantics): dizi, dizgi ve yapı gibi değer tipleri atamada kopyalanır ve kopya bağımsız bir değerdir. Bir değişkeni diğerine atadığında, ikisinden birini değiştirmek öbürünü etkilemez. Bu özellik tasarımın “yanlışlıkla paylaşılan durum olmasın” kaygısından gelir. Ölçüm sorusu başka: kopya, kaynağı üreten ifadeyi yeniden değerlendirir mi, yoksa yalnız var olan değeri mi çoğaltır?

Aşağıdaki blok bir diziyi bir Probe ile üretir, duplicate adlı ikinci bir değişkene kopyalar ve iki diziyi birbirinden bağımsız biçimde değiştirir. Ardından her iki değişkeni okur; okumalar iki kullanım sayılır.

// Copy.swift — kopya ifadeyi tekrar calistirmaz
let sourceProbe = Probe("kaynak-dizi")
var original = sourceProbe.eval([1, 2, 3])
var duplicate = original
duplicate.append(4)
original.append(9)
_ = sourceProbe.use { original.count }
_ = sourceProbe.use { duplicate.count }
print(original, duplicate)
sourceProbe.report()
[1, 2, 3, 9] [1, 2, 3, 4]
kaynak-dizi: 1 degerlendirme, 2 kullanim -> hemen

original dizisine eklenen 9, duplicate dizisinde görünmedi; duplicate dizisine eklenen 4 de original dizisinde yok. İki değişken atama anında ayrıldı ve her biri kendi yoluna gitti. Ölçüm satırı ise ifadenin yalnız bir kez değerlendirildiğini söylüyor: atama bir değeri çoğaltır, onu üreten ifadeyi yeniden çalıştırmaz. Değerlendirme var tanımında, iki okumadan önce olduğu için an hemen çıkar.

Bu ayrım güvenlik hedefini somutlaştırır. Eğer atama kaynağı yeniden hesaplasaydı, her kopya sonucunu başka bir anda üretebilirdi; ölçülen davranışta kopya, atama anındaki değerin bir görüntüsüdür. Sonradan yapılan değişiklikler iki tarafa ayrı ayrı uygulanır.

DT3. Kopyanın bağımsızlığı bir Int dizisiyle ölçüldü; içinde sınıf örneği taşıyan bir dizinin kopyasında elemanların paylaşılması bu dersin konusu değildir.

Sabit Bağlama, Nesneyi Dondurmaz

Güvenlik hedefinin bir başka ucu let ile ilgilidir ve sık yanlış okunur. Bir sınıf örneğini let ile bağladığında, bağlanan şey örneğe giden referanstır. Değişken yerine sabit yazmak, holder adının başka bir nesneyi göstermesini engeller, ama nesnenin kendi var özelliklerini kilitlemez. Aynı kuralı değer tipleri için ters yönde okuyabilirsin: bir yapıyı let ile bağlarsan özelliklerini de değiştiremezsin; sınıfta ise bu yasak yoktur.

Aşağıdaki blok küçük bir sınıf tanımlar, onu let ile bağlar ve yine de içindeki sayacı iki kez atayarak değiştirir. Sayaç değeri bir Probe üzerinden gelir; iki ayrı atama iki ayrı değerlendirme getirir.

// Holder.swift — let nesneyi dondurmaz
final class Holder {
    var count = 0
}
let holderProbe = Probe("sinif-sayaci")
let anchor = Holder()
_ = holderProbe.use { anchor.count = holderProbe.eval(5) }
_ = holderProbe.use { anchor.count = holderProbe.eval(8) }
print(anchor.count)
holderProbe.report()
8
sinif-sayaci: 2 degerlendirme, 2 kullanim -> her-kullanimda

anchor bir let sabiti olduğu halde anchor.count iki kez atandı ve son değer olan 8 okundu. Sabit olan, anchor adının bağlandığı referanstır; nesnenin içi değişkendir. Ölçüm satırı iki değerlendirme gösteriyor, çünkü iki atama iki ayrı eval çağrısı yaptı: let kuralı burada atamanın sayısını kısıtlamıyor, çünkü atanan şey anchor değil, anchor.count alanıdır. Bu yüzden an her-kullanimda olur.

Tasarım açısından çıkarım şudur: let, bir bağlamanın yeniden yapılamayacağı güvencesidir; nesnenin değişmezliği güvencesi değildir. Değişmezlik istiyorsan alanları da let yap ya da değer tipi kullan.

DT4. Sınıf örneği tek bir alanla ölçüldü; let ile bağlı değer tipinde aynı atamanın derlenmediği iddiası bu dersin dışında, KAYNAK düzeyinde bir bilgidir.

Tip Çıkarımı Anı Değiştirmez

İfade gücü hedefinin en görünür parçası tip çıkarımıdır: let size = 12 yazarsan derleyici Int tipini kendisi bulur, sen yazmak zorunda kalmazsın. İstersen tipi açıkça da yazarsın: let size: Int = 12. Burada akla gelen soru, çıkarımın bir çalışma zamanı maliyeti ya da zamanlaması olup olmadığıdır. Tip çıkarımı derleme sırasında biter; çalışan program tipin nasıl bulunduğunu bilmez.

Aşağıdaki blok aynı değeri iki yolla tanımlar: biri çıkarımla, biri açık tip bildirimiyle. Her biri kendi Probe’una bağlıdır ve iki kez okunur; type(of:) ile iki değişkenin tipi de yazdırılır.

// Inference.swift — cikarim ve acik tip ayni ani verir
let inferredProbe = Probe("cikarilan-tip")
let declaredProbe = Probe("acik-tip")
let inferred = inferredProbe.eval(42)
let declared: Int = declaredProbe.eval(42)
_ = inferredProbe.use { inferred + 1 }
_ = inferredProbe.use { inferred + 2 }
_ = declaredProbe.use { declared + 1 }
_ = declaredProbe.use { declared + 2 }
print(type(of: inferred), type(of: declared))
inferredProbe.report()
declaredProbe.report()
Int Int
cikarilan-tip: 1 degerlendirme, 2 kullanim -> hemen
acik-tip: 1 degerlendirme, 2 kullanim -> hemen

İki değişkenin tipi aynı çıktı, ve iki Probe aynı anı verdi: ikisi de tek değerlendirme, iki kullanım, hemen. Açık tip bildirimi ifadeyi başka bir zamana taşımadı; yalnız derleyiciye ne bulması gerektiğini söyledi. Bu yüzden kursun ölçü ekseni tip çıkarımına duyarsızdır: ifadenin ne zaman değerlendirildiğini belirleyen şey tipin yazılı olup olmaması değil, ifadenin hangi bağlamda durduğudur.

DT5. Tip çıkarımı bu derste yalnız Int literaliyle ölçüldü; çıkarımın başka literal ve ifade çeşitlerinde zamanlamaya dokunmadığı iddiası bu dersin dışındadır.

Tembel Dönüşüm Çağrılana Kadar Bekler

Tasarımın ifade gücü hedefi, kısa yazılan şeyin ne zaman çalıştığı sorusunu da doğurur. Bir dizi üzerinde map çağırdığında dönüşüm hemen ve her eleman için yapılır. Aynı dizinin lazy görünümü üzerinde map çağırırsan, dönüşüm tanımlandığı anda hiçbir eleman için çalışmaz; yalnız bir eleman istendiğinde, o eleman için çalışır. Bu, daha önce gördüğümüz lazy var özelliğinin dizi karşılığı değildir ama aynı sezgiyi taşır: iş, sonucu isteyene kadar ertelenebilir.

Aşağıdaki blok iki Probe kurar. İstekli dönüşümün gövdesi eagerProbe’a, tembel dönüşümün gövdesi lazyProbe’a değerlendirme yazar. Dönüşümlerin hemen ardından ve tembel görünümden iki eleman çekildikten sonra sayaçlar yazdırılır. evaluations özelliği Probe içinde okunabilir bir sayaçtır.

// LazyMap.swift — istekli ve tembel donusum
let eagerProbe = Probe("istekli-donusum")
let lazyMapProbe = Probe("tembel-donusum")
let values = [1, 2, 3]
let eagerDoubled = values.map { eagerProbe.eval($0 * 2) }
let lazyDoubled = values.lazy.map { lazyMapProbe.eval($0 * 2) }
print(eagerProbe.evaluations, lazyMapProbe.evaluations)
_ = lazyMapProbe.use { Array(lazyDoubled.prefix(2)) }
print(eagerProbe.evaluations, lazyMapProbe.evaluations)
_ = lazyMapProbe.use { Array(lazyDoubled.prefix(2)) }
print(eagerDoubled, Array(lazyDoubled.prefix(1)))
lazyMapProbe.report()
3 0
3 2
[2, 4, 6] [2]
tembel-donusum: 5 degerlendirme, 2 kullanim -> her-kullanimda

İlk satır 3 0 çıktı: istekli map üç elemanın üçünü hemen dönüştürdü, tembel map ise henüz hiçbirine dokunmadı. İkinci satırda tembel sayaç 2 oldu, çünkü prefix(2) yalnız iki elemanın dönüşümünü istedi; üçüncü eleman hiç hesaplanmadı. Aynı prefix(2) ikinci kez çağrılınca dönüşüm yeniden çalıştı, çünkü tembel görünüm sonuçları saklamaz; son report satırındaki beş değerlendirme bunu gösteriyor: iki, iki ve son prefix(1) çağrısından bir.

İfade gücü hedefi bu iki davranışı birbirinden ayırt etmeyi sana bırakır. İstekli map sonucu bir kez üretir ve saklar. Tembel görünüm yazmayı kısa tutar, ama her çekişte işi yeniden yapabilir. Hangisinin doğru olduğu, sonucun kaç kez okunacağına ve gövdenin yan etkisi olup olmadığına bağlıdır.

DT6. Tembel dönüşüm yalnız prefix ile çekilen elemanlar için ölçüldü; lazy görünümün filter ya da zincirlenmiş dönüşümlerde sayıyı nasıl değiştirdiği bu dersin dışındadır.

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

Kural sayar, niyeti okumaz. Aynı let ifadesini iki ayrı sabit olarak, iki ayrı Probe ile yazarsan ikisi de hemen çıkar. Aynı ifadeyi tek Probe’a bağlarsan, ikinci eval sayıyı ikiye çıkarır ve kural her-kullanimda der; kodun niyeti değişmemiştir, ölçüm düzeneği ise değişmiştir.

// Twin.swift — ayni ifade, iki probe ve tek probe
let first = Probe("birinci-sabit")
let second = Probe("ikinci-sabit")
let shared = Probe("ortak-sabit")

let a = first.eval(5)
let b = second.eval(5)
let c = shared.eval(5)
let d = shared.eval(5)

for probe in [first, second, shared] {
    _ = probe.use { a + b + c + d }
    _ = probe.use { a + b + c + d }
    probe.report()
}
birinci-sabit: 1 degerlendirme, 2 kullanim -> hemen
ikinci-sabit: 1 degerlendirme, 2 kullanim -> hemen
ortak-sabit: 2 degerlendirme, 2 kullanim -> her-kullanimda

DT7. Bu ölçüm, ifadenin yazılışını değil, eval çağrısının sayısını okur; Probe’u dil özelliği saymıyoruz.

Özet

  • Swift güvenliği derleyiciyle sağlar: atanmamış sabit okunamaz, isteğe bağlı olmayan değer nil olamaz.
  • İfade gücü tip çıkarımıdır; başarım hedefi bu derste ölçülmez.
  • Kursun ölçü ekseni: bu ifade ne zaman değerlendiriliyor? Dört an: hemen, ilk kullanımda, her kullanımda, hiç.
  • Probe kuralı sayıyı okur; niyeti okumaz.
  • Bu dersin anı: hemen (let sabiti, tanımlandığı satırda).

Sonraki Adım

Sabit tanımlandığı anda değerlendirilir; peki var yeniden atanınca ne olur, ve bir sabit tanımlandıktan sonra atanabilir mi? Sıradaki ders, sabitlerle değişkenlerin atama anlarını ölçüyor.

İ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