Ders 03 / 21
Temel Tipler
`Int`, `Double`, `Bool` ve `String` tiplerini tanıtır; dönüşümün açık olduğunu ve bir dönüşüm ifadesinin hemen değerlendirildiğini ölçer.
Yayın Güncelleme
Bu derste
- `Int`, `Double`, `Bool` ve `String` tiplerinin rollerini ayırt etmek
- Tipler arası dönüşümün açık yazıldığını ve ne zaman yapıldığını söylemek
- Kayan noktalı karşılaştırmanın neden beklenmedik sonuç verdiğini açıklamak
- Demet elemanlarının soldan sağa ve okunmasa da değerlendirildiğini göstermek
İçindekiler
Sabit ve değişkenin atama anını gördük: tanımlandığı satırda, hemen. Şimdi atanan değerin kendisine bakıyoruz. Swift’in dört temel tipi var: tam sayı için Int, kayan noktalı sayı için Double, mantıksal değer için Bool ve dizgi (string) için String. Bu dersin sorusu şu: bir tipi başka bir tipe çeviren ifade ne zaman değerlendiriliyor?
Dönüşüm Açıktır
Swift iki tipi kendiliğinden karıştırmaz. Int ile Double toplamak istersen derleyici programı reddeder: aşağıdaki parça Swift 6.4’te derlenmez, bu yüzden çıktısızdır.
let count = 1 let size = 2.5 print(count + size)
Çözüm dönüşümü yazmaktır: Double(count). Dönüşüm kendi başına bir ifadedir ve bir let içine yazılırsa o satırda bir kez değerlendirilir. Bu dersin ölçümü bunu doğrular. Önce her derste aynı kalan yardımcı dosya.
// 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 iki Probe ile başlar: biri dönüşümü, ikincisi bir demet elemanını izler. Dönüşümün hemen yanında iki kayan noktalı gözlem ve bir Int.max basımı durur; çünkü Double karşılaştırmasının neden sürpriz verdiği bu dersin konusudur. marked yardımcı fonksiyonu demet elemanlarının sırasını print ile gösterir. unusedProbe ise hiç okunmayan bir demet elemanını izler.
// main.swift — donusum ve demet ani
let conversionProbe = Probe("tip-donusumu")
let tupleProbe = Probe("demet-elemani")
let unusedProbe = Probe("okunmayan-eleman")
let wholeCount = 7
let ratio = conversionProbe.eval(Double(wholeCount) / 2)
_ = conversionProbe.use { ratio }
_ = conversionProbe.use { ratio }
print(ratio)
print(Int.max)
print(0.1 + 0.2 == 0.3)
print(0.1 + 0.2)
func marked(_ n: Int) -> Int { print("deger \(n)"); return n }
let triple = (marked(1), marked(2), marked(3))
print(triple.1)
let record = (size: tupleProbe.eval(3), spare: unusedProbe.eval(9))
_ = tupleProbe.use { record.size }
_ = tupleProbe.use { record.size }
let allProbes = [conversionProbe, tupleProbe, unusedProbe]
for probe in allProbes { probe.report() }
tally(allProbes)
3.5 9223372036854775807 false 0.30000000000000004 deger 1 deger 2 deger 3 2 tip-donusumu: 1 degerlendirme, 2 kullanim -> hemen demet-elemani: 1 degerlendirme, 2 kullanim -> hemen okunmayan-eleman: 1 degerlendirme, 0 kullanim -> hemen hemen: 3 ilk-kullanimda: 0 her-kullanimda: 0 hic: 0
ratio yedinin ikiye bölümünü 3.5 olarak verir, çünkü bölme Double üzerinde yapıldı. Int.max en büyük Int değerini basar; bu değerin bir fazlası bir program hatasıdır ve dersin ölçümünden çıkarıldı. 0.1 + 0.2 == 0.3 beklentinin tersine false verir: toplama tam 0.3’ü değil, ona çok yakın bir sayıyı üretir. Kayan noktalı sayı ikilik tabanda tam gösterilemediği için eşitlik yerine fark ölçülür; bu bir Swift özelliği değil, Double temsilinin sonucudur.
Dönüşüm satırı bir kez, tanımlandığı satırda, ilk kullanımdan önce değerlendirildi: tip-donusumu iki kullanımla hemen çıktı. Demet elemanları ise soldan sağa değerlendirildi: deger 1, deger 2, deger 3. Demet (tuple), birden çok değeri tek bir değerde toplayan yapıdır; elemanlar tanım satırında hazır olur.
DT16. Int.max yazdırması yalnız 64 bitlik Int için geçerlidir; ölçümde kullanılan derleyici 64 bitlik bir hedef için derledi.
DT17. 0.1 + 0.2 == 0.3 iki sabit ifadeyle ölçüldü; hesabı yapan değerlendirmenin derleyicide mi koşumda mı yapıldığı bu derste ayırt edilmiyor.
Sayısal Tip Başlatıcıları
Swift farklı sayısal tipler arasında örtük dönüşüm yapmaz; dönüşümü sen yazarsın. Bunun aracı tipin başlatıcısıdır: Double(7), Int(3.9), UInt8(200). Ondalıklı bir değeri Int yapan başlatıcı kesirli kısmı atar, yani sıfıra doğru keser; Int(3.9) 3, Int(-3.9) ise -3 verir. Yuvarlama gerekiyorsa ayrı bir işlem yazarsın.
Bu davranışı ölçmek için aşağıdaki blok ondalıklı bir sayıyı bir Probe üzerinden üretir ve iki farklı başlatıcıdan geçirir. Başlatıcı ifadenin kendisidir; bu yüzden sayıyı üreten eval çağrısı başlatıcının argümanı içinde durur.
// Convert.swift — baslaticilarla donusum
let truncateProbe = Probe("kesme-donusumu")
let widenProbe = Probe("genisletme-donusumu")
let cut = Int(truncateProbe.eval(3.9))
let negativeCut = Int(truncateProbe.eval(-3.9))
let wide = Double(widenProbe.eval(7))
let small = UInt8(widenProbe.eval(200))
_ = truncateProbe.use { cut + negativeCut }
_ = truncateProbe.use { cut - negativeCut }
_ = widenProbe.use { wide }
_ = widenProbe.use { small }
print(cut, negativeCut, wide, small)
truncateProbe.report()
widenProbe.report()
3 -3 7.0 200 kesme-donusumu: 2 degerlendirme, 2 kullanim -> her-kullanimda genisletme-donusumu: 2 degerlendirme, 2 kullanim -> her-kullanimda
Int(3.9) 3, Int(-3.9) -3 verdi; yani dönüşüm sıfıra doğru keser. Double(7) 7.0 yazıldı ve UInt8(200) 200 olarak kaldı. İki ölçüm satırı da iki değerlendirme gösterdi: her başlatıcı çağrısı argümanını bir kez değerlendirdi ve her Probe iki başlatıcıya bağlı olduğu için sayı ikiye çıktı. Bu yüzden iki satır da her-kullanimda okunur; sayı dönüşümün değil, ölçüm düzeneğinin eseridir.
Burada önemli olan başlatıcının hata verebildiği yerdir: UInt8(300) gibi bir değer bu tipe sığmaz ve program durur. Bu yüzden dönüşüm yazarken değerin hedef tipe sığdığını önceden bilmek gerekir. Ders bu durumu çalıştırmaz.
DT18. Dönüşümler yalnız tipe sığan değerlerle ölçüldü; sığmayan değerin durumu bu derste çalıştırılmaz, yalnız anlatılır.
Taşma İşleçleri
Tam sayı tiplerinin sınırı vardır. UInt8 değeri 0 ile 255 arasında kalır. Normal + işleci sonucu bu sınırın dışına taşırsa program durur. Swift bunun yanında taşmayı kabul eden işleçler de sunar: &+, &- ve &*. Bu işleçler sonucu tipin kapasitesine göre başa sarar. & öneki, taşmayı bilerek istediğini söylemenin yoludur; sessizce olmaz, işleci seçerek olur.
Aşağıdaki blok UInt8 değerine bir fonksiyon içinde &+ ile sayı ekler. Eklenen sayı bir Probe üzerinden geldiği için her çağrı bir değerlendirme sayılır. İlk çağrı 250 değerine 10 ekler ve sınırı aşar; ikinci çağrı güvenli bir değerle çalışır.
// Wrap.swift — tasma isleci basa sarar
func bump(_ value: UInt8, probe: Probe) -> UInt8 {
value &+ probe.eval(10)
}
let wrapProbe = Probe("tasma-isleci")
print(wrapProbe.use { bump(250, probe: wrapProbe) })
print(wrapProbe.use { bump(1, probe: wrapProbe) })
let floor: UInt8 = 0
print(floor &- 1, UInt8.max)
wrapProbe.report()
4 11 255 255 tasma-isleci: 2 degerlendirme, 2 kullanim -> her-kullanimda
250 &+ 10 260 olmadı; sonuç 4 çıktı, çünkü 255’ten sonra sayım 0’a döndü. 1 &+ 10 ise normal 11 verdi. Üçüncü satırdaki 0 &- 1 değeri UInt8.max ile aynı olan 255 çıktı: sınırın altına inen sayı üst uca sarıldı. Ölçüm satırı iki değerlendirme gösteriyor: her bump çağrısı operandı bir kez değerlendirdi, bu yüzden an her-kullanimda olur.
Taşma işleçleri ölçülebilir, çünkü program durmaz. Normal + ile aynı taşma ise durdurur ve bu yüzden bu derste çalıştırılmaz. Hangisini seçeceğin niyetine bağlıdır: sarmayı istiyorsan &+, taşmanın hata olmasını istiyorsan + yaz.
DT19. Taşma işleci UInt8 ile ölçüldü; işaretli tiplerin &+ ile sarma davranışı bu derste ölçülmedi.
Tip Takma Adı ve Sınırlar
Her sayısal tipin bir alt ve üst sınırı vardır ve tip bunları min ile max statik özellikleri olarak sunar: Int8.min -128, Int8.max 127, UInt8.max 255. Bir tipin sınırını bilmek taşma sorusunu yerinde yanıtlar. Kodda bir tipe başka bir ad vermek istediğinde typealias kullanırsın: typealias Pixel = UInt8. Takma ad yeni bir tip değildir; aynı tipin ikinci adıdır, sınırlarını ve davranışını aynen taşır.
Aşağıdaki blok Pixel takma adını tanımlar, onunla bir sabit bağlar ve sabitin tipini ve sınırlarını yazdırır. Sabit bir Probe’dan gelir ve iki kez okunur.
// Alias.swift — tip takma adi ayni tiptir
typealias Pixel = UInt8
let aliasProbe = Probe("takma-ad-sabiti")
let brightness: Pixel = aliasProbe.eval(200)
_ = aliasProbe.use { brightness }
_ = aliasProbe.use { brightness &+ 100 }
print(type(of: brightness), Pixel.max, Int8.min)
aliasProbe.report()
UInt8 255 -128 takma-ad-sabiti: 1 degerlendirme, 2 kullanim -> hemen
type(of: brightness) UInt8 yazdı, yani takma ad yeni bir tip yaratmadı. Pixel.max 255, Int8.min ise -128 çıktı. Probe tek bir değerlendirme gösterdi ve sabit kullanımdan önce kurulduğu için an hemen oldu. Takma ad yalnız ad düzeyinde bir kolaylıktır; çalışma zamanında hiçbir şey eklemez.
Sınır özelliklerini bilmek, dönüşüm ve taşma kararlarının temelidir. Bir değerin sığıp sığmayacağını max ile karşılaştırarak önceden yoklayabilirsin; böylece sığmayan bir dönüşümü hiç çalıştırmazsın.
DT20. Sınır değerleri UInt8 ve Int8 ile gösterildi; diğer tam sayı tiplerinin sınırları bu dersin dışındadır.
Ondalığı Tam Sayıya Çevirmek
Bir Double değerini Int yapmanın tek bir yolu yoktur ve üç yaygın yol farklı sonuç verir. Int(value) ondalık kısmı atar, yani sıfıra doğru keser. value.rounded() en yakın tam değere yuvarlar; tam ortadaki değerde sıfırdan uzağa gider. value.rounded(.down) ise daima küçük olana iner. Üçü de önce bir Double üretir ya da doğrudan Int verir; hangi yolu seçtiğin, negatif sayılarda sonucu değiştirir.
Aşağıdaki blok bir sayıyı üç yolla çevirir. Çevrilecek değer bir Probe’dan gelir ve fonksiyon pozitif ve negatif bir değerle çağrılır.
// Rounding.swift — ondaligi tam sayiya cevirmek
func convert(_ value: Double, probe: Probe) -> (Int, Int, Int) {
let source = probe.eval(value)
return (Int(source), Int(source.rounded()), Int(source.rounded(.down)))
}
let roundProbe = Probe("yuvarlama")
print(roundProbe.use { convert(2.5, probe: roundProbe) })
print(roundProbe.use { convert(-2.5, probe: roundProbe) })
roundProbe.report()
(2, 3, 2) (-2, -3, -3) yuvarlama: 2 degerlendirme, 2 kullanim -> her-kullanimda
Pozitif 2.5 için üç yol (2, 3, 2) verdi: kesme 2, en yakına yuvarlama 3, aşağı yuvarlama 2. Negatif -2.5 için sonuç (-2, -3, -3) oldu. Kesme sıfıra doğru gittiği için -2 verdi; en yakına yuvarlama tam ortada sıfırdan uzağa gittiği için -3; aşağı yuvarlama ise daima küçük olana indiği için yine -3 verdi. Pozitif sayıda kesme ile aşağı yuvarlama aynı sonucu verir, negatif sayıda ayrılırlar. Probe iki çağrıya karşılık iki değerlendirme gösterdi, bu yüzden an her-kullanimda okunur.
Dönüşüm yolunu seçerken sorman gereken şey, negatif değerlerin de gelip gelmediğidir. Gelmiyorsa üç yolun ikisi aynı işi görür; geliyorsa her birinin kuralı ayrı bir karar demektir.
DT21. Yuvarlama 2.5 ve -2.5 ile ölçüldü; diğer yuvarlama kuralları (.up, .toNearestOrEven) bu dersin dışındadır.
Ondalık Eşitliği
İkili kayan nokta sayıları, onluk tabandaki bazı basit kesirleri tam olarak tutamaz. 0.1, 0.2 ve 0.3 bunların en bilinenidir: her biri en yakın temsil edilebilir değere yuvarlanır. İki sayıyı toplayıp üçüncüyle == ile karşılaştırmak bu yüzden beklenmedik bir sonuç verebilir. Ondalık sayıları karşılaştırırken tam eşitlik yerine, kabul edilebilir bir fark sınırı seçersin.
Aşağıdaki blok bu durumu ölçer. Toplamın ilk terimi bir Probe’dan gelir. Blok toplamı, tam eşitlik sonucunu ve farkın küçük bir sınırın altında kalıp kalmadığını yazdırır.
// Equality.swift — ondalik esitligi
func sum(probe: Probe) -> Double {
probe.eval(0.1) + 0.2
}
let equalProbe = Probe("ondalik-toplam")
let total = equalProbe.use { sum(probe: equalProbe) }
print(total)
print(total == 0.3)
print(abs(total - 0.3) < 0.000001)
equalProbe.report()
0.30000000000000004 false true ondalik-toplam: 1 degerlendirme, 1 kullanim -> ilk-kullanimda
Toplam 0.30000000000000004 yazdırdı, tam eşitlik false verdi, küçük sınırla yapılan karşılaştırma ise true çıktı. 0.1 ve 0.2 ikili tabanda tam yazılamadığı için toplamları 0.3 ile aynı değerde buluşmadı; fark son basamaktadır. Bu yüzden ondalık sayılarda ==, hesaplanmış değerler için güvenilir bir sınama değildir. Ölçüm tek değerlendirme gösterdi: ilk terim Probe’dan bir kez alındı ve sonraki karşılaştırmalar yeniden hesaplamadı, an ilk-kullanimda olur.
Sınırın büyüklüğü işe göre seçilir; burada 0.000001 yeterli bir ölçüydü. Para gibi kuruşu önemli olan değerlerde ondalık tip yerine tam sayı birimleriyle çalışmak daha güvenlidir.
Yuvarlama kuralını sonuca bakarak değil, niyete göre seç. Parayı ya da sayacı aşağı kesmek istiyorsan .down, en yakın değeri istiyorsan rounded(), yalnız tam kısmı almak istiyorsan Int(value) yazarsın. Üçünü aynı sanmak, negatif değer ilk geldiği gün hatalı bir sonuca yol açar. Çevrilen Double tam sayı aralığının dışındaysa Int(value) programı durdurur; bu ders bu durumu çalıştırmaz.
Ondalık eşitlikte de aynı ilke geçerlidir: sonucu ne olması gerektiğine göre bir sınırla karşılaştır, tam eşitliğe güvenme. Sınırı bir isimli sabite almak, aynı ölçünün birden çok yerde tutarlı kalmasını sağlar.
DT22. Eşitlik Double ile ölçüldü; Float ve Decimal gibi tiplerde aynı toplamın sonucu bu dersin dışındadır.
Sınırlayıcı Ölçüm
okunmayan-eleman satırına bak: demet içinde bir eleman tanımlandı, bir kez değerlendirildi, hiç okunmadı. Kural hemen der, çünkü değerlendirme kullanımdan önce yapıldı ve hiç kullanım yoktu. Yani demet elemanı okunmasa da değerlendirilir; hic yalnızca ifade hiç değerlendirilmediğinde ortaya çıkar. Bu satır, ölçüm koşulunun neden “en az iki kullanım” istediğini de gösterir: sıfır kullanımda kural, boşa değerlendirilen bir ifadeyi gerçek bir hemen’den ayıramaz.
DT23. Sıfır kullanım bu satırda bilerek bırakıldı; derste başka hiçbir gözlem sıfır kullanım taşımaz.
Özet
Int,Double,BoolveStringdört temel tiptir; tipler kendiliğinden karıştırılmaz.- Dönüşüm açıktır:
Double(count)bir ifadedir ve tanımlandığı satırda değerlendirilir. 0.1 + 0.2 == 0.3kayan noktalı gösterim yüzündenfalseverir.- Demet elemanları soldan sağa ve okunmasa da değerlendirilir.
- Bu dersin anı: hemen (dönüşüm ifadesi
letsatırında bir kez).
Sonraki Adım
String bu derste yalnız adıyla geçti. Bir sonraki ders dizgiye iniyor: bir karakter nedir, count ile utf8.count neden ayrışır ve dizgi içine yerleştirilen ifade kaç kez kuruluyor?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.