Ders 06 / 21
Denetim Akışı
Koşulları, döngüleri ve desen eşlemeli `switch` ifadesini tanıtır; ilk eşleşmeden sonraki `where` koşullarının hiç değerlendirilmediğini ölçer.
Yayın Güncelleme
Bu derste
- `if` / `else if` zincirini ve `switch` içindeki desen eşlemeyi kullanmak
- İlk eşleşmeden sonraki koşulların değerlendirilmediğini göstermek
- `for ... where` ve `while` koşullarının kaç kez değerlendirildiğini okumak
- `switch` ifadesinin tümlük şartını açıklamak
İçindekiler
İşleçler iki ifadeyi birleştirdi ve sağ taraf gerekmediğinde hiç değerlendirilmedi. Denetim akışı aynı fikri bir adım yukarı taşır: hangi dalın çalışacağına koşullar karar verir ve ilk uygun dalı bulan yapı geri kalanına bakmaz. Bu dersin sorusu şu: bir zincirde, eşleşmeden sonra gelen koşullar ne zaman değerlendiriliyor?
Koşullar, Döngüler ve switch
if bir Bool koşulu sorar; else if zinciri koşulları yukarıdan aşağı dener. for n in 1...3 bir aralıktaki her değer için gövdeyi çalıştırır, while koşul doğru olduğu sürece sürer. switch ise bir değeri desenlerle (pattern) karşılaştırır. Bir case bir değer, bir aralık, bir demet ya da where ile süslenmiş bir koşul olabilir. Swift’te case gövdeleri bir sonrakine düşmez; ilk eşleşen çalışır ve switch biter.
switch tükenmeli olmak zorundadır: her olası değer bir case tarafından karşılanmalı ya da bir default bulunmalıdır. Aşağıdaki parça Int için yalnız 1 ve 2’yi ele alır. Bu parça Swift 6.4’te derlenmez; derleyici onu reddeder, bu yüzden çıktısızdır.
let n = 3
switch n {
case 1: print("bir")
case 2: print("iki")
}
Hata gerçek bir güvencedir: yeni bir değer eklendiğinde karşılanmayan switch derlenmez. Çözüm default eklemek ya da kalan değerleri bir desenle yakalamaktı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)")
}
}
Ölçüm bloğu iki zincir kurar. İlkinde grade fonksiyonu 75 puanı değerlendirir: ilk koşul yanlış, ikincisi doğrudur ve üçüncüsüne hiç gelinmez. İkincisinde describe fonksiyonu -5 değerini bir switch ile sınar: ilk case’in where koşulu doğrudur, sonraki case’in where koşulu hiç sorulmaz.
// main.swift — zincirde ilk eslesmeden sonrasi
func grade(_ score: Int, _ second: Probe, _ third: Probe) -> String {
if score >= 90 {
return "A"
} else if second.eval(score >= 70) {
return "B"
} else if third.eval(score >= 50) {
return "C"
}
return "F"
}
func describe(_ n: Int, _ first: Probe, _ later: Probe) -> String {
switch n {
case let x where first.eval(x < 0):
return "negatif"
case let x where later.eval(x > 100):
return "buyuk"
default:
return "diger"
}
}
for n in 1...3 { print("adim", n) }
let secondProbe = Probe("else-if-ikinci-kosul")
let thirdProbe = Probe("else-if-ucuncu-kosul")
let firstWhereProbe = Probe("switch-ilk-where")
let laterWhereProbe = Probe("switch-sonraki-where")
for _ in 1...2 {
let letter = secondProbe.use { thirdProbe.use { grade(75, secondProbe, thirdProbe) } }
print(letter)
let kind = firstWhereProbe.use { laterWhereProbe.use { describe(-5, firstWhereProbe, laterWhereProbe) } }
print(kind)
}
let allProbes = [secondProbe, thirdProbe, firstWhereProbe, laterWhereProbe]
for probe in allProbes { probe.report() }
tally(allProbes)
adim 1 adim 2 adim 3 B negatif B negatif else-if-ikinci-kosul: 2 degerlendirme, 2 kullanim -> her-kullanimda else-if-ucuncu-kosul: 0 degerlendirme, 2 kullanim -> hic switch-ilk-where: 2 degerlendirme, 2 kullanim -> her-kullanimda switch-sonraki-where: 0 degerlendirme, 2 kullanim -> hic hemen: 0 ilk-kullanimda: 0 her-kullanimda: 2 hic: 2
else-if-ikinci-kosul her çağrıda değerlendirildi, çünkü birinci koşul yanlış olduğu için zincir ona geldi. else-if-ucuncu-kosul ise iki çağrıda da hic çıktı: ikinci koşul zaten doğruydu ve zincir orada bitti. Aynı şey switch için de geçerli: ilk where her seferinde sınandı, sonraki where hiç sınanmadı. Yani where koşulundaki bir yan etki, daha önceki bir case eşleştiyse hiç gerçekleşmez.
DT39. Ölçüm tek bir giriş değeriyle (75 ve -5) yapıldı; başka girişlerde hangi koşulun hic kalacağı değişir.
DT40. switch’in kaynak sırasıyla yukarıdan aşağı denendiği ölçümden çıkarıldı; derleyicinin sırayı değiştirip değiştirmediği ayrıca ölçülmedi.
guard ile Erken Çıkış
guard bir koşulu doğrulamaya yarar ve koşul sağlanmazsa kapsamdan çıkmayı zorunlu kılar. Söz dizimi guard koşul else { çıkış } biçimindedir; else bloğu return, break, continue ya da bir durduran çağrıyla bitmek zorundadır. Koşul sağlanırsa kontrol guard satırının ardından devam eder ve koşulda bağlanan adlar o kapsamın geri kalanında görünür. if ile aynı işi görür, ama niyeti tersine çevirir: önce istenmeyen durum elenir, sonra asıl iş düz bir yolda yazılır.
Aşağıdaki blok pozitif sayıyı kabul eden bir fonksiyon yazar. Geçerli yol bir Probe’a, geçersiz yol başka bir Probe’a yazar. Fonksiyon üç sayıyla çağrılır ve her çağrıdan önce iki Probe birer kullanım kaydeder.
// Guard.swift — guard ile erken cikis
func check(_ number: Int, valid: Probe, invalid: Probe) -> String {
guard number > 0 else { return invalid.eval("gecersiz") }
return valid.eval("gecerli")
}
func observe<T>(_ watched: [Probe], _ work: () -> T) -> T {
for probe in watched { _ = probe.use { 0 } }
return work()
}
let validProbe = Probe("gecerli-yol")
let invalidProbe = Probe("gecersiz-yol")
for number in [5, -2, 7] {
print(observe([validProbe, invalidProbe]) {
check(number, valid: validProbe, invalid: invalidProbe)
})
}
validProbe.report()
invalidProbe.report()
gecerli gecersiz gecerli gecerli-yol: 2 degerlendirme, 3 kullanim -> her-kullanimda gecersiz-yol: 1 degerlendirme, 3 kullanim -> ilk-kullanimda
Üç sayı gecerli, gecersiz, gecerli sonuçlarını verdi. Geçerli yol iki kez, geçersiz yol bir kez çalıştı: gecerli-yol iki değerlendirme ve üç kullanımla her-kullanimda, gecersiz-yol ise bir değerlendirme ve üç kullanımla ilk-kullanimda çıktı. Kullanım sayısı üç çıktı, çünkü observe her çağrıdan önce kullanımı kaydetti. Bir yolun ürettiği ifade, yalnız o yol seçildiğinde değerlendirilir; seçilmeyen yolun ifadesi hiç çalışmaz.
guard, hangi yolun hangi anda çalıştığını okunur kılar: istisna yolu en başta durur ve geride kalan satırlar yalnız koşul sağlandığında çalışır. Ölçüm bu okunuşu doğruluyor.
DT41. guard yalnız bir tam sayı koşuluyla ölçüldü; isteğe bağlı değerlerle guard let kullanımı sonraki konunun işidir.
switch Desenleri
switch ifadesi bir değeri sırayla desenlerle karşılaştırır. Desenler yalnız değişmez olmak zorunda değildir: demet desenleri birden çok değeri aynı anda karşılaştırır, let ile değer bağlanır ve where bir koşul ekler. Örneğin case (let x, 0) ikinci elemanı sıfır olan her demeti yakalar ve ilk elemanı x adına bağlar. Eşleşen ilk durum çalışır ve diğerleri hiç değerlendirilmez.
Aşağıdaki blok bir noktanın sınıfını bulan bir fonksiyon yazar. Üç durumun her biri kendi Probe’una yazar. Fonksiyon üç noktayla çağrılır: orijin iki kez, yatay eksendeki bir nokta bir kez; üçüncü durum hiç eşleşmez.
// Pattern.swift — switch desenleri
func classify(_ x: Int, _ y: Int, watched: [Probe]) -> String {
switch (x, y) {
case (0, 0):
return watched[0].eval("orijin")
case (let along, 0):
return watched[1].eval("yatay \(along)")
case (_, let height) where height > 5:
return watched[2].eval("yuksek \(height)")
default:
return "diger"
}
}
func observe<T>(_ watched: [Probe], _ work: () -> T) -> T {
for probe in watched { _ = probe.use { 0 } }
return work()
}
let originProbe = Probe("orijin-durumu")
let axisProbe = Probe("eksen-durumu")
let highProbe = Probe("yuksek-durum")
let cases = [originProbe, axisProbe, highProbe]
for point in [(0, 0), (4, 0), (0, 0)] {
print(observe(cases) { classify(point.0, point.1, watched: cases) })
}
for probe in cases { probe.report() }
orijin yatay 4 orijin orijin-durumu: 2 degerlendirme, 3 kullanim -> her-kullanimda eksen-durumu: 1 degerlendirme, 3 kullanim -> ilk-kullanimda yuksek-durum: 0 degerlendirme, 3 kullanim -> hic
Üç çağrı orijin, yatay 4, orijin sonuçlarını verdi. orijin-durumu iki değerlendirmeyle her-kullanimda, eksen-durumu tek değerlendirmeyle ilk-kullanimda, yuksek-durum ise sıfır değerlendirmeyle hic çıktı. Üçüncü durum where koşuluna rağmen hiç denenmedi, çünkü önceki iki durum noktaları zaten yakalamıştı. Yani switch sırayla bakar, ilk eşleşmeyi alır ve gerisine dokunmaz.
Bu kural desenleri sıralarken önem taşır: genel bir durumu özel bir durumdan önce yazarsan özel durum hiç çalışmaz. Derleyici kapsam eksikliğini hata olarak reddeder, ama sıra yanlışlığını bazı durumlarda bulamaz.
DT42. Desenler demet ve where ile ölçüldü; numaralandırma durumları ve aralık desenleri bu dersin dışındadır.
Etiketli Deyimler
İç içe döngülerde break yalnız en içteki döngüyü bitirir. Dış döngüyü bitirmek için döngüye bir etiket verirsin: outer: for row in 1...3 { ... }. İçerideki break outer bu etikete atlar ve dış döngü biter. continue de aynı etiketle dış döngünün bir sonraki yinelemesine geçer. Etiket, hangi döngüden söz ettiğini açıkça yazmanın yoludur.
Aşağıdaki blok çarpım tablosunda belirli bir çarpımı arayan bir fonksiyon yazar. Her çarpım bir Probe üzerinden hesaplanır; böylece dönülen yineleme sayısı ölçülür. Fonksiyon iki hedefle çağrılır.
// Label.swift — etiketli break ile arama
func search(target: Int, probe: Probe) -> String {
var found = "yok"
outer: for row in 1...3 {
for col in 1...3 {
if probe.eval(row * col) == target {
found = "\(row)x\(col)"
break outer
}
}
}
return found
}
let searchProbe = Probe("carpim-aramasi")
print(searchProbe.use { search(target: 4, probe: searchProbe) })
print(searchProbe.use { search(target: 6, probe: searchProbe) })
searchProbe.report()
2x2 2x3 carpim-aramasi: 11 degerlendirme, 2 kullanim -> her-kullanimda
İlk arama 2x2, ikincisi 2x3 buldu. Toplam on bir değerlendirme gerekti: ilk aramada beş çarpım hesaplandı (1, 2, 3, 2, 4), ikincisinde altı (1, 2, 3, 2, 4, 6). break outer bulunur bulunmaz iki döngüyü birden bitirdi, bu yüzden kalan çarpımlar hiç hesaplanmadı. Etiketsiz break yalnız iç döngüyü bitirirdi ve dış döngü bir sonraki satırla devam ederdi. An her-kullanimda okunur.
Etiketli çıkış, aramayı gereksiz çarpımlardan korur. Etiket kodun kaç döngü atladığını da gösterir; okuyan hangi döngünün bittiğini adından bilir.
DT43. Etiketli break iki iç içe döngüyle ölçüldü; continue ile etiket kullanımı bu dersin dışındadır.
repeat-while ile En Az Bir Tur
while döngüsü koşulu önce denetler; koşul baştan yanlışsa gövde hiç çalışmaz. repeat-while bunu tersine çevirir: gövde önce çalışır, koşul sonra denetlenir. Böylece gövde, koşul ne olursa olsun, en az bir kez çalışır. Bir değeri en az bir kez okumak ya da bir işlemi bir kez yapıp sonra devam edip etmeyeceğine karar vermek gerektiğinde bu biçim doğru araçtır.
Aşağıdaki blok iki döngüyü aynı baştan yanlış koşulla çalıştırır. Her gövde bir sayaca değerini bir Probe’dan gelen bir ile artırır. Biri while, öbürü repeat-while ile yazılmıştır.
// Repeat.swift — repeat-while en az bir tur
let whileProbe = Probe("while-govdesi")
let repeatProbe = Probe("repeat-govdesi")
var whileCount = 0
var repeatCount = 0
_ = whileProbe.use {
while whileCount < 0 { whileCount += whileProbe.eval(1) }
}
_ = repeatProbe.use {
repeat { repeatCount += repeatProbe.eval(1) } while repeatCount < 0
}
print(whileCount, repeatCount)
whileProbe.report()
repeatProbe.report()
0 1 while-govdesi: 0 degerlendirme, 1 kullanim -> hic repeat-govdesi: 1 degerlendirme, 1 kullanim -> ilk-kullanimda
Aynı baştan yanlış koşulla iki döngü 0 1 verdi: while sayacı hiç artırmadı, repeat-while bir kez artırdı. while-govdesi sıfır değerlendirmeyle hic, repeat-govdesi ise bir değerlendirmeyle ilk-kullanimda çıktı. repeat-while döngüsünde koşul gövdeden sonra denetlendiği için gövdenin ifadesi, koşul yanlış olsa bile bir kez çalıştı.
Seçim sorusu şudur: gövde, koşuldan önce en az bir kez çalışmak zorunda mı? Bir değeri okuyup sonra karar veriyorsan repeat-while, koşul baştan sağlanmıyorsa hiçbir şey yapmak istemiyorsan while doğru araçtır.
DT44. repeat-while baştan yanlış bir koşulla ölçüldü; koşulun doğru kaldığı döngülerde iki biçimin aynı sonucu verdiği bu dersin dışındadır.
Sınırlayıcı Ölçüm
Zincirdeki koşullar bir kez sorulup bırakılırdı. Döngülerde koşul her turda yeniden sorulur. for ... where her yinelemede koşulu değerlendirir; yani where içindeki ifade bu satırda her-kullanimda olur. while ise bir ince ayrıntı taşır: koşul, döngüden çıkarken de bir kez daha değerlendirilir, çünkü döngüden çıkmanın tek yolu koşulun false dönmesidir. Üç tur süren bir while bu yüzden koşulu dört kez değerlendirir.
// main.swift — dongu kosullari
let whereProbe = Probe("for-where-kosulu")
let whileProbe = Probe("while-kosulu")
for _ in 1...2 {
var evens: [Int] = []
whereProbe.use {
for n in 1...4 where whereProbe.eval(n % 2 == 0) { evens.append(n) }
}
print(evens)
whileProbe.use {
var i = 0
while whileProbe.eval(i < 3) { i += 1 }
}
}
let allProbes = [whereProbe, whileProbe]
for probe in allProbes { probe.report() }
tally(allProbes)
[2, 4] [2, 4] for-where-kosulu: 8 degerlendirme, 2 kullanim -> her-kullanimda while-kosulu: 8 degerlendirme, 2 kullanim -> her-kullanimda hemen: 0 ilk-kullanimda: 0 her-kullanimda: 2 hic: 0
İki tur boyunca for ... where koşulu sekiz kez, her tur dört yineleme için birer kez değerlendirildi. while koşulu ise iki turun her birinde üç yineleme yaptığı hâlde sekiz değerlendirme aldı: yineleme sayısı artı bir. Fazladan olan değerlendirme, döngüyü bitiren false sorgusudur.
DT45. while için “yineleme artı bir” sonucu bu üç yinelemelik örnekte ölçüldü; break ya da return ile çıkan bir döngüde ek sorgu yoktur, ancak bu derste ölçülmedi.
Özet
if/else ifzinciri koşulları yukarıdan aşağı dener ve ilk doğruda durur.switchtükenmelidir; eksikcasederleyicide reddedilir.- İlk eşleşmeden sonraki
wherekoşulları veelse ifkoşulları hiç değerlendirilmez. for ... wherekoşulu her yinelemede,whilekoşulu yineleme sayısından bir fazla değerlendirilir.- Bu dersin anı: hiç (eşleşmeden sonraki
where).
Sonraki Adım
Bir koşul yalnız bir dalın çalışıp çalışmayacağını söyler. Bir de çağrı sırasında ne zaman değerlendirildiği sorulan ifadeler var: fonksiyonun varsayılan argümanı. Sıradaki ders, argüman etiketlerini ve varsayılan değerleri ölçüyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.