İçeriğe geç
academia.sh

Ders 02 / 21

Sabitler ve Değişkenler

`let` ve `var` ile tip çıkarımını, açık tip bildirimini ve atama anını ölçer: sabit hemen mi değerlendirilir, atama her seferinde mi?

Yayın Güncelleme

Bu derste

  • `let` ile `var` arasındaki farkı atama davranışı üzerinden açıklamak
  • Tip çıkarımı ile açık tip bildirimini karşılaştırmak
  • Ertelenmiş başlatmanın ve yeniden atamanın değerlendirme anını yorumlamak
  • Başka dosyadaki küresel sabitin ilk erişimde kurulduğunu sıralama çıktısından okumak
İçindekiler

Önceki derste bir let sabitinin tanımlandığı satırda hemen değerlendirildiğini gördün. Bu ders o gözlemi üç yönden zorluyor: sabite tip yazmak bir şeyi değiştirir mi, var her atamada ne yapar, ve değer tanımlandıktan sonra atanırsa an kayar mı? Değişken ve tip kavramlarını Programlama Temelleri kursundan (M01/K02) biliyorsun; burada yalnız Swift’e özgü kısmı ölçüyoruz.

Sabit, Değişken ve Tip

Swift’te let bir kez bağlanan sabiti, var sonradan değişebilen değişkeni tanımlar. Tipi çoğu zaman yazmazsın: derleyici sağ taraftan çıkarır (tip çıkarımı, type inference). İstersen tipi iki noktadan sonra yazarsın (açık tip bildirimi, explicit type annotation). Sabite ikinci kez atama yapmak derleyicide reddedilir: aşağıdaki parça Swift 6.4’te derlenmez, bu yüzden çıktısızdır.

let rate = 4
rate = 5

var için aynı atama serbesttir. Fark yalnız yazım değil: var satırı her atamada sağ tarafı yeniden değerlendirir.

Üç Atama Biçimi

Önce Probe.swift yardımcı dosyası; her derste aynı kalır.

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

Şimdi üç Probe: biri tipi çıkarılan sabiti, biri tanımı ile ataması ayrılmış sabiti, biri de üç kez atanan değişkeni izler.

// main.swift — sabit ve degisken atama anlari
let inferredProbe = Probe("cikarilan-sabit")
let deferredProbe = Probe("ertelenen-sabit")
let counterProbe = Probe("degisken-atamasi")

let rate = inferredProbe.eval(4)
let explicitRate: Double = 4
_ = inferredProbe.use { rate }
_ = inferredProbe.use { rate }
print("\(type(of: rate)) \(type(of: explicitRate))")

let isWeekend = false
let level: Int
if isWeekend {
    level = deferredProbe.eval(1)
} else {
    level = deferredProbe.eval(2)
}
_ = deferredProbe.use { level }
_ = deferredProbe.use { level }

var total = counterProbe.eval(0)
total = counterProbe.eval(total + 5)
total = counterProbe.eval(total + 7)
_ = counterProbe.use { total }
_ = counterProbe.use { total }

let allProbes = [inferredProbe, deferredProbe, counterProbe]
for probe in allProbes { probe.report() }
tally(allProbes)
Int Double
cikarilan-sabit: 1 degerlendirme, 2 kullanim -> hemen
ertelenen-sabit: 1 degerlendirme, 2 kullanim -> hemen
degisken-atamasi: 3 degerlendirme, 2 kullanim -> her-kullanimda
hemen: 2
ilk-kullanimda: 0
her-kullanimda: 1
hic: 0

İlk satır tipleri gösterir: tipi yazılmayan 4 bir Int, Double olarak bildirilen aynı sayı bir Double oldu. Tip çıkarımı değerlendirme anını değiştirmedi; çıkarılan sabit tanımlandığı satırda, ilk kullanımdan önce değerlendirildi, yani hemen.

level ilginç bir durumdur: tanım (let level: Int) ile değer arasına bir if girdi, ama atama kullanımdan önce yapıldı. Dalların ikisi de eval çağırır, çalışan ise biri; sayı bir, an yine hemen. Derleyici atamadan önce okumayı reddeder (önceki dersin ilk parçası), bu yüzden tanım ile atama arasında okuma olamaz; ama atama bir kullanım sarmalının içinde yapılırsa ölçüm ilk-kullanimda verir (aşağıdaki Ertelenmiş İlk Değer bölümü).

var ise başka. total üç kez atandı ve her atama sağ tarafı yeniden değerlendirdi; kural bunu her-kullanimda okur. Değişken tek kez bağlanan bir sabit değildir; her atama yeni bir değerlendirmedir.

DT8. counterProbe atamaları kullanımdan önce yapılır, ama üç değerlendirme olduğu için kural her-kullanimda der; sayı birden çoksa “önce mi sonra mı” bakılmaz.

DT9. type(of:) çıktısı yalnız Int ve Double adlarını basar; tip adı ölçüm eksenine değil, çıkarımın sonucuna işaret eder.

Tip Çıkarımı ve Açık Tip Bildirimi

Bir sabit ya da değişken tanımlarken tipi yazmak zorunda değilsin. let attempts = 3 satırında derleyici sağdaki değişmezden Int çıkarır; buna tip çıkarımı (type inference) denir. İstersen tipi iki noktadan sonra açıkça yazarsın: let ratio: Double = 3. Buna açık tip bildirimi (explicit type annotation) denir. İkisinin farkı yalnız tipi kimin belirlediğidir: çıkarımda değerden tip, açık bildirimde tipten değer biçimi türer.

Fark en çok değişmez bir sayı yazdığında görünür. 3 değişmezi çıkarımda Int olur, ama sol tarafa Double yazarsan aynı değişmez 3.0 olarak okunur. Aşağıdaki blok aynı 3 değişmezini iki yolla bağlar, her ikisini bir Probe’dan geçirir ve iki kez okur.

// Literal.swift — ayni degismez, iki tip
let inferredProbe = Probe("cikarilan-int")
let declaredProbe = Probe("acik-double")
let attempts = inferredProbe.eval(3)
let ratio: Double = declaredProbe.eval(3)
_ = inferredProbe.use { attempts }
_ = inferredProbe.use { attempts }
_ = declaredProbe.use { ratio }
_ = declaredProbe.use { ratio }
print(attempts, ratio)
print(type(of: attempts), type(of: ratio))
inferredProbe.report()
declaredProbe.report()
3 3.0
Int Double
cikarilan-int: 1 degerlendirme, 2 kullanim -> hemen
acik-double: 1 degerlendirme, 2 kullanim -> hemen

attempts 3 olarak, ratio ise 3.0 olarak yazıldı; type(of:) satırı tiplerin Int ve Double olduğunu doğruluyor. Değişmezin yazılışı aynıydı, ama bağlamdan gelen tip onu iki farklı değere çevirdi. İki Probe ise aynı anı verdi: ikisi de hemen. Tip yalnız değerin ne olduğunu belirler, ne zaman değerlendirileceğini belirlemez. Derleyici tipi kodu çalıştırmadan bulur; çalışma sırasında geriye kalan yalnız tek bir eval çağrısıdır.

Bu yüzden açık tip bildirimi iki iş görür. Birincisi, çıkarımın seçeceğinden farklı bir tip istediğinde onu söylemektir. İkincisi, kodu okuyana değerin niyetini göstermektir. Ölçüm ekseni açısından ise ikisi eşittir.

DT10. Çıkarım ve bildirim aynı 3 değişmeziyle ölçüldü; daha karmaşık bir ifadede tipin ne kadar erken bulunduğu bu dersin dışındadır.

Demet Çözümleme ve Tek Satırda Çok Tanım

Swift bir sabit ya da değişken satırında birden çok ad tanımlamana izin verir. Virgülle ayırarak var left = 0, right = 0 yazabilirsin; her ad kendi ifadesiyle ayrı ayrı bağlanır. Başka bir yol, bir demeti açmaktır: let (low, high) = (1, 9) iki sabit tanımlar ve demetin elemanlarını sırayla onlara dağıtır. Bu söz dizimi demet çözümleme (tuple decomposition) olarak anılır.

Aklına gelen soru şudur: iki ad aynı satırda tanımlandığında ifadeleri aynı anda mı değerlendirilir, yoksa biri öbüründen önce mi? Aşağıdaki blok iki ayrı Probe ile iki adı bir demetle tanımlar, ardından iki var adı tek satırda tanımlayıp sırayla atar.

// Decompose.swift — demet cozumleme ve tek satirda cok tanim
let lowProbe = Probe("alt-sinir")
let highProbe = Probe("ust-sinir")
let (low, high) = (lowProbe.eval(1), highProbe.eval(9))
_ = lowProbe.use { low }
_ = lowProbe.use { low + 1 }
_ = highProbe.use { high }
_ = highProbe.use { high - 1 }
var left = 0, right = 0
left = lowProbe.eval(low)
right = highProbe.eval(high)
_ = lowProbe.use { left }
_ = highProbe.use { right }
print(low, high, left, right)
lowProbe.report()
highProbe.report()
1 9 1 9
alt-sinir: 2 degerlendirme, 3 kullanim -> her-kullanimda
ust-sinir: 2 degerlendirme, 3 kullanim -> her-kullanimda

Demetin iki elemanı da tanım satırında, hiçbir okumadan önce değerlendirildi. Ama bu bir tek değerlendirme değildi: lowProbe ve highProbe birer değerlendirme gösterdi, sonra left ve right atamaları ikinci birer değerlendirme ekledi. Bu yüzden iki ölçüm satırı da iki değerlendirme, üç kullanım ve her-kullanimda verdi. Tek satırda tanım ya da demet çözümleme, ifadeleri tek bir adımda birleştirmez; her eleman kendi ifadesi kadar değerlendirilir.

Burada ilke şudur: bir ifadenin kaç kez değerlendirildiğini, onu kaç satırda yazdığın değil, eval benzeri kaç değerlendirme noktası olduğu belirler. Demet çözümlemesi yazımı kısaltır, çalışma sırasını değiştirmez.

DT11. Demet çözümleme iki elemanlı ve sabit değişmezle ölçüldü; iç içe demetlerin çözülme sırası bu dersin dışındadır.

Gölgeleme

Bir iç kapsamda dış kapsamla aynı adı yeniden tanımlayabilirsin. Buna gölgeleme (shadowing) denir: iç kapsamdaki ad, dış kapsamdakini kapsam bitene kadar görünmez kılar. Dış değişken ya da sabit hiçbir şekilde değişmez; yalnız iç kapsamdaki okumalar yeni ada gider. Bu davranış, bir değeri geçici olarak dönüştürüp aynı adı kullanmanın güvenli yoludur.

Gölgeleme yeni bir atama değildir. amount ikinci kez let ile tanımlandığında dış amount olduğu gibi kalır; yeni tanım ikinci bir değerlendirmedir ve yalnız kendi kapsamında yaşar. Aşağıdaki blok bir do kapsamı içinde dış amount sabitini gölgeler, iç değeri yazdırır, kapsamdan çıkar ve dış değeri yeniden yazdırır.

// Shadow.swift — golgeleme dis degeri degistirmez
let outerProbe = Probe("dis-kapsam")
let innerProbe = Probe("ic-kapsam")
let amount = outerProbe.eval(1)
do {
    let amount = innerProbe.eval(amount + 10)
    _ = innerProbe.use { amount }
    _ = innerProbe.use { amount * 2 }
    print(amount)
}
_ = outerProbe.use { amount }
_ = outerProbe.use { amount + 5 }
print(amount)
outerProbe.report()
innerProbe.report()
11
1
dis-kapsam: 1 degerlendirme, 2 kullanim -> hemen
ic-kapsam: 1 degerlendirme, 2 kullanim -> hemen

İç kapsamda amount 11 çıktı: iç tanımın sağ tarafındaki amount hâlâ dış sabitti, çünkü iç ad henüz bağlanmamıştı. Kapsamdan çıkınca amount yeniden 1 oldu; gölgeleme dış sabiti hiç değiştirmedi. İki Probe ayrı ayrı bir değerlendirme ve iki kullanım gösterdi: ikisi de hemen. Gölgeleme yeni bir değerlendirme ekler, ama bir yeniden atama olmadığı için dış tarafın sayısını artırmaz.

Pratikte gölgeleme, değişken adlarını çoğaltmadan bir değeri dönüştürmenin sade yoludur. Karşılığında okuyanın hangi amount ile karşılaştığını bilmesi gerekir; kapsamı kısa tutmak bu yükü azaltır.

DT12. Gölgeleme do kapsamıyla ölçüldü; fonksiyon parametresini ya da döngü değişkenini gölgelemenin davranışı bu dersin dışındadır.

Ertelenmiş İlk Değer

Bir let sabitine tanımlandığı satırda değer vermek zorunda değilsin. Tipini yazıp değeri sonra atayabilirsin, yeter ki derleyici her yolda sabite tam olarak bir kez değer verildiğini görebilsin. Bu kural, let bildiriminin “değişmez” anlamını korur: sabit ilk değerini alana kadar okunamaz, aldıktan sonra yeniden atanamaz. Dallanan bir akışta her dal sabiti bir kez doldurur ve sabit, dalın sonunda hazırdır.

Bunun ölçülecek bir yan etkisi vardır: yalnız seçilen dalın ifadesi çalışır. Aşağıdaki blok bir fonksiyon yazar. Fonksiyonda label sabiti önce bildirilir, sonra if ile bir dalda Probe’dan gelen değerle, öbür dalda sabit bir dizgiyle doldurulur. Fonksiyon önce true, sonra false ile çağrılır.

// Deferred.swift — ertelenmis ilk deger
func pick(open: Bool, probe: Probe) -> String {
    let label: String
    if open {
        label = probe.eval("acik")
    } else {
        label = "kapali"
    }
    return label
}
let pickProbe = Probe("ertelenmis-sabit")
print(pickProbe.use { pick(open: true, probe: pickProbe) })
print(pickProbe.use { pick(open: false, probe: pickProbe) })
pickProbe.report()
acik
kapali
ertelenmis-sabit: 1 degerlendirme, 2 kullanim -> ilk-kullanimda

true çağrısı acik, false çağrısı kapali verdi. Her çağrıda yeni bir label sabiti tanımlandığı ve her biri bir kez doldurulduğu için derleyici bunu kabul etti. Probe ise iki kullanıma karşılık tek değerlendirme gösterdi: probe.eval yalnız true dalında çalıştı, false dalının sabit dizgisi Probe’a hiç uğramadı. İlk kullanımda değerlendirilen ve sonra bir daha çalışmayan bir ifade, ilk-kullanimda anının işaretidir; burada ikinci çağrı o ifadeye hiç ulaşmadı.

Bu davranış, ertelenmiş ilk değerin bir kural olduğunu gösterir: her yol sabiti tam olarak bir kez doldurur, doldurmayan yol hiçbir şey hesaplamaz. Sabitin değeri, akış hangi dala girerse onun ifadesinden gelir. Derleyici bir dalda atamayı unutursan, sabiti o noktadan sonra kullanmana izin vermez.

Bu kural bir fonksiyonun dönüş değerini hazırlarken de işe yarar: sabit bildirilir, dallar onu doldurur ve fonksiyon sonunda tek bir return kalır. Yerine var kullanıp sonradan değiştirmek de çalışırdı, ama let derleyiciye ve okuyana değerin bir kez atandığını söyler.

DT13. Ertelenmiş ilk değer iki dalla ve bir fonksiyon içinde ölçüldü; üst düzey kodda ya da özelliklerde aynı yapının kuralları bu dersin dışındadır.

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

Bir sabitin hemen değerlendirilmesi her yerde geçerli değildir. Başka dosyada tanımlı bir küresel sabit, ilk erişimde kurulur. Aşağıdaki yardımcı dosya başlatma kapanışına bir print koyar; kapanış let tanımının içindedir.

// Config.swift — baska dosyadaki kuresel sabit
let appName: String = {
    print("appName kuruluyor")
    return "academia"
}()

main.swift ise aynı kalıpta bir yerel sabit tanımlar ve iki sabitin ne zaman kurulduğunu satır sırasıyla gösterir.

// main.swift — kuresel ve yerel sabitin kurulus ani
print("baslangic")
let localName: String = {
    print("localName kuruluyor")
    return "academia"
}()
print("ikinci satir")
print("okunan: \(appName)")
print("yerel: \(localName)")
baslangic
localName kuruluyor
ikinci satir
appName kuruluyor
okunan: academia
yerel: academia

localName main.swift üst düzeyinde, yazıldığı satırda kuruldu (hemen); appName ise ikinci satırdan sonra, ilk okunduğu print içinde kuruldu (ilk-kullanimda). Aynı yazılışın iki farklı anı vardır.

DT14. Probe bu gözleme giremez: Probe Sendable değildir ve Swift 6 kurallarında küresel sabit olarak tutulamaz; bu yüzden an burada print sırasından okunur, sayıdan değil.

DT15. Bu ölçüm yalnız bir küresel sabit ve yalnız bir yardımcı dosya içindir; sıralama kuralının başka durumlarını (örneğin birden çok küresel sabitin birbirine bağlı kurulması) ölçmüyoruz.

Özet

  • let bir kez bağlanır, ikinci atama derleyicide reddedilir; var yeniden atanabilir.
  • Tip çıkarımı ile açık tip bildirimi değerlendirme anını değiştirmez.
  • Ertelenmiş başlatma yalnız atamadan sonra okunabilir; atama kullanımdan önceyse hemen, bir kullanım sarmalının içindeyse ilk-kullanimda verir.
  • Her var ataması sağ tarafı yeniden değerlendirir; kural bunu her-kullanimda okur.
  • Bu dersin anı: hemen (tanımlandığı ya da atandığı satırda değerlendirilen sabit).

Sonraki Adım

Sabitlerin ve değişkenlerin atama anını ölçtük. Bir sonraki ders, üzerlerine konan değerlerin tiplerine bakıyor: Int, Double, Bool ve String arasındaki dönüşüm ne zaman yapı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