Ders 05 / 18
Sıfır Değerler
On beş yazılmamışlık tek bir çerçeveden geçiriliyor: sekizi dil tarafından tanımlı bir karşılık buluyor, dördü derleyici tarafından reddediliyor, üçü çalışma zamanına açık kalıyor. Sınırlayıcı ölçüm: nil bir dilime eklenebiliyor ama nil bir sözlüğe yazılamıyor — ikisi de aynı biçimde sıfır değer.
İçindekiler
Önceki ders var b int8 yazıp hiç değer vermeseydik ne olacağını sormadan bitti. Bu soru,
kursun açılışından beri ertelenen asıl soru: bir değişken ilk değeri verilmeden bırakıldığında
dil orada ne buluyor? Bu ders bu soruyu tek bir örnekte değil, on beş ayrı yerde soruyor ve
üç sınıftan birine yazıyor — tanımlı, dil yazılmayanın yerine bir değer koyuyor;
reddedildi, derleyici yazılmamış olmayı kabul etmiyor; açıkta, sonuç çalışma zamanına
kalıyor. Kursun geri kalanı boyunca atıf yapılacak sayılar burada üretiliyor.
Programlama Temelleri kursu (M01/K02) bir değişkenin bir adla bağlanmasını Python’la kurdu; orada bir ad, bir değer verilmeden var olamazdı. Bu kavram burada tekrar kurulmaz — farkı tek cümleyle yazmak yeter: Go’da bir değişken değer verilmeden de var olabiliyor, çünkü tip sistemi her tipe önceden bir sıfır değer atamış durumda. Bu dersin ölçtüğü, o sıfır değerin her tipte ne olduğu ve her durumda gerçekten “orada” olup olmadığıdır.
Kurgu
Go’da bir programı okurken en çok işe yarayan soru, yazılanın ne yaptığı değil, yazılmayanın ne olduğudur. İlk değeri verilmeyen her değişkenin tanımlı bir değeri var, kullanılmayan bir değişken derlemeyi durduruyor, olmayan bir anahtar iki yanıt birden dönüyor, hiç argüman verilmeyen bir değişken argüman listesi boş bir dilim oluyor. Aynı dilde, yazılmayan bazı şeylerin karşılığı hiç yok ve program çalışırken duruyor.
Ölçüm bu üçlüyü tek bir soruya indiriyor: her yazılmamışlık için, dil bir şey mi koyuyor, derleyici mi reddediyor, yoksa sonuç açıkta mı kalıyor? Yanıtı bulmanın ikinci yarısı derleyiciyi program içinden çağırıyor: bir kaynak parçası geçici bir pakete yazılıyor, derleyici koşturuluyor ve yalnız geçip geçmediği okunuyor — hata metni hiç okunmuyor, çünkü ölçülen şey “neden reddedildi” değil, “reddedildi mi”.
Çekirdek: Üç Sınıfa Ayıran Bir Gözlemci
// eksik.go — yazilmayan sey ne oluyor: dil tanimli bir sey mi koyuyor, derleyici mi reddediyor, yoksa acikta mi kaliyor
package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
)
var tanimli, reddedildi, acikta int
func gozle(ad, sinif, kanit string) {
switch sinif {
case "tanimli":
tanimli++
case "reddedildi":
reddedildi++
default:
acikta++
}
fmt.Printf("%-38s %-11s %s\n", ad, sinif, kanit)
}
func ozet() {
fmt.Printf("yazilmayan: %d tanimli, %d reddedildi, %d acikta\n", tanimli, reddedildi, acikta)
}
func derlenirMi(kaynak string) bool {
dizin, err := os.MkdirTemp("", "sinama")
if err != nil {
return false
}
defer os.RemoveAll(dizin)
os.WriteFile(filepath.Join(dizin, "go.mod"), []byte("module sinama\n\ngo 1.24\n"), 0o644)
os.WriteFile(filepath.Join(dizin, "kod.go"), []byte(kaynak), 0o644)
cmd := exec.Command("go", "build", "./...")
cmd.Dir = dizin
cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
return cmd.Run() == nil
}
func panikliyorMu(f func()) (p bool) {
defer func() {
if recover() != nil {
p = true
}
}()
f()
return false
}
DT13. gozle, her gözlemi bir sınıfa yazan tek giriş noktasıdır; sayaçları tutan üç
paket düzeyi değişken (tanimli, reddedildi, acikta) yalnız burada artıyor, böylece
ozet çağrıldığında toplamlar hep gözlemlerle tutarlı kalıyor.
DT14. panikliyorMu, bir işlevi çalıştırıp panikleyip paniklemediğini recover ile
yakalıyor ve yalnız panik=true/panik=false döndürüyor — hiçbir yığıt izi basılmıyor. Bu,
kursun panik ölçen her yerinde geçerli tek yöntem.
Bu iki yardımcı işlev, dersin ölçtüğü üç sınıfın her birine bir araç sağlıyor: gozle ve
ozet sınıflandırmayı ve toplamı tutuyor, panikliyorMu açıkta kalan davranışı yakalıyor,
derlenirMi (bir önceki bölümde tanımlı) reddi ölçüyor. Üçü birlikte, on beş satırlık ölçümün
gövdesini tek bir çağrı biçimine indiriyor: her satır gozleye bir ad, bir sınıf ve bir kanıt
veriyor, geri kalan her şeyi bu üç yardımcı hallediyor.
On Beş Yazılmamışlık
// olcum.go — on bes yazilmamislik, uc sinifa bolunur
package main
import "fmt"
type Kayit struct {
Ad string
Sayi int
Etkin bool
}
func adliDonus() (sonuc int, hata error) { return }
func degisken(sayilar ...int) []int { return sayilar }
func main() {
fmt.Println("-- yazilmayanin karsiligi --")
var tamsayi int
gozle("ilk deger verilmeyen tam sayi", "tanimli", fmt.Sprint(tamsayi))
var dilim []int
gozle("ilk deger verilmeyen dilim", "tanimli",
fmt.Sprint("nil=", dilim == nil, " uzunluk=", len(dilim), " eklenebilir=", append(dilim, 1)))
var esleme map[string]int
gozle("ilk deger verilmeyen eslemeden okuma", "tanimli", fmt.Sprint(esleme["yok"]))
yazmaPanikledi := panikliyorMu(func() { esleme["yeni"] = 1 })
gozle("ilk deger verilmeyen eslemeye yazma", "acikta", fmt.Sprint("panik=", yazmaPanikledi))
var isaretci *Kayit
okumaPanikledi := panikliyorMu(func() { _ = isaretci.Sayi })
gozle("ilk deger verilmeyen isaretciden okuma", "acikta", fmt.Sprint("panik=", okumaPanikledi))
var kayit Kayit
gozle("yazilmayan yapi alanlari", "tanimli", fmt.Sprintf("%q %d %t", kayit.Ad, kayit.Sayi, kayit.Etkin))
s, h := adliDonus()
gozle("adlandirilmis donusun yazilmamasi", "tanimli", fmt.Sprint(s, " ", h == nil))
gozle("degisken argumana hic arguman", "tanimli",
fmt.Sprint("nil=", degisken() == nil, " uzunluk=", len(degisken())))
kaynak := []int{10, 20, 30, 40}
gozle("dilim sinirlarinin yazilmamasi", "tanimli", fmt.Sprint(kaynak[:], " ", kaynak[1:], " ", kaynak[:2]))
deger, varMi := esleme["yok"]
gozle("olmayan anahtarin iki yanit vermesi", "tanimli", fmt.Sprint(deger, " ", varMi))
metin := "çay"
gozle("dizgide karakter yerine bayt", "acikta",
fmt.Sprint("uzunluk=", len(metin), " ilk=", metin[0], " rune=", len([]rune(metin))))
gozle("kullanilmayan degisken", siniflaKaynak(`package sinama
func F() { x := 1 }`), "")
gozle("kullanilmayan ice aktarim", siniflaKaynak(`package sinama
import "fmt"
func F() {}`), "")
gozle("donus degerinin yazilmamasi", siniflaKaynak(`package sinama
func F() int { }`), "")
gozle("ortuk tip donusumu", siniflaKaynak(`package sinama
func F() { var a int32 = 1; var b int64 = a; _ = b }`), "")
ozet()
}
func siniflaKaynak(kaynak string) string {
if derlenirMi(kaynak) {
return "acikta"
}
return "reddedildi"
}
-- yazilmayanin karsiligi -- ilk deger verilmeyen tam sayi tanimli 0 ilk deger verilmeyen dilim tanimli nil=true uzunluk=0 eklenebilir=[1] ilk deger verilmeyen eslemeden okuma tanimli 0 ilk deger verilmeyen eslemeye yazma acikta panik=true ilk deger verilmeyen isaretciden okuma acikta panik=true yazilmayan yapi alanlari tanimli "" 0 false adlandirilmis donusun yazilmamasi tanimli 0 true degisken argumana hic arguman tanimli nil=true uzunluk=0 dilim sinirlarinin yazilmamasi tanimli [10 20 30 40] [20 30 40] [10 20] olmayan anahtarin iki yanit vermesi tanimli 0 false dizgide karakter yerine bayt acikta uzunluk=4 ilk=195 rune=3 kullanilmayan degisken reddedildi kullanilmayan ice aktarim reddedildi donus degerinin yazilmamasi reddedildi ortuk tip donusumu reddedildi yazilmayan: 8 tanimli, 4 reddedildi, 3 acikta
Son dört satır, ilk derste ayrıntılı ölçülen dört rettir; burada listenin tamamlayıcısı olarak
yer alıyor ve aynı sınıfa (reddedildi) düşüyor. Geri kalan on bir satır bu derse özgü ve altı
ayrı tipi kapsıyor: tam sayı, dilim, sözlük, işaretçi, yapı, isimlendirilmiş dönüş değeri,
değişken sayıda argüman, dizgi. Her satır aynı soruyu tek bir tipe soruyor — bu tipin ilk
değeri verilmediğinde ne oluyor — ve yanıtı gozleye üç parçayla teslim ediyor: bir ad, bir
sınıf, bir kanıt. Kanıt sütunu, iddiayı doğrulayan gerçek değerleri taşıyor; bir sonraki bölüm
bu kanıtları tek tek okuyor.
Sayıların Okunması
Birinci iddia: Go’da “başlatılmamış” diye bir durum yok. On beş yazılmamışlığın sekizinde
dil yazılmayanın yerine tanımlı bir şey koyuyor: bir tam sayının sıfır değeri 0, bir dilimin
sıfır değeri nil ama uzunluğu 0 ve eklenebilir, bir sözlükten okuma 0 dönüyor, bir yapının
alanları kendi sıfır değerlerini taşıyor, adlandırılmış bir dönüş yazılmadığında 0 ve nil
dönüyor, değişken sayıda argümana hiç argüman verilmediğinde parametre nil bir dilim oluyor,
dilim sınırları yazılmadığında baştan ve sona kadar uzanıyor, olmayan bir anahtar 0 ve
false dönüyor. Sekizi de bir kolaylık değil bir sözleşme: okuyan taraf ne bulacağını
kaynağa bakmadan bilir.
Bu sekizinin ortak yanı, sıfır değerin tip başına farklı görünmesi ama her zaman
öngörülebilir olması. Bir tam sayı için sıfır değer sayısal sıfırdır; bir dizgi için boş
dizgidir (""); bir mantıksal değer için falsetur; bir işaretçi, dilim, sözlük ya da işlev
için nildir. Dördü de aynı sözcükle (nil) anılmasa da aynı fikri taşıyor: “henüz hiçbir
şeye bağlı değil”. nilin kendisi tek bir değer değildir — bir işaretçinin nili bellekte
hiçbir adresi göstermediği, bir dilimin nili hiçbir alt diziye sahip olmadığı, bir sözlüğün
nili hiçbir depolama alanı ayrılmadığı, bir işlevin nili çağrılabilecek hiçbir kod
bulunmadığı anlamına geliyor. Dördü de aynı sözcükle yazdırılıyor ama dördü de kendi tipinin
içinde ayrı bir yokluğu temsil ediyor.
İkinci iddia: derleyicinin reddettiği dört şeyin dördü de başka dillerde uyarı düzeyindedir. Kullanılmayan değişken, kullanılmayan içe aktarım, dönüş değerinin yazılmaması, örtük tip dönüşümü — dördü de kaynağı derlenmez yapıyor. Reddetmek bir katılık gösterisi değil, yazılmayanın karşılığını daraltma kararı: dil bu dört durumda “tanımlı bir şey koymak” yerine “hiç izin vermemek”i seçiyor.
Bu dördü, ilk sekizle karşılaştırıldığında ilginç bir yer değiştirme gösteriyor. İlk sekizde dil, yazılmayan bir şeye rağmen kaynağı çalıştırmaya izin veriyor ve boşluğu kendi dolduruyor. Bu dört durumda ise dil, boşluğu doldurmak yerine kaynağı hiç çalıştırmıyor. İkisi de “yazılmayanın karşılığı” olsa da, biri çalışma zamanına taşınan bir değer, öbürü derleme zamanında kapanan bir kapı.
Üçüncü iddia: tanımlılık okuma yönünde çalışıyor, yazma ve yorumlama yönünde bitiyor.
Açıkta kalan üç durumun üçü de bunu gösteriyor. İlk değeri verilmeyen bir sözlükten okumak
tanımlıdır ama ona yazmak programı durduruyor — panik. İlk değeri verilmeyen bir işaretçi
tanımlı bir değerdir (nil) ama okunduğunda durur — yine panik. Bir dizginin uzunluğu
tanımlıdır ama indekslendiğinde gelen şey karakter değil bayttır — "çay" dört bayt, üç
rune ve ilk bayt 195 (bir çok baytlı kodlamanın ilk parçası, tek başına bir harf değil).
Sınırlayıcı Ölçüm: “Tanımlı” Kullanışlı Demek Değildir
Sekiz tanımlı satırın hepsi aynı derecede kullanışlı değil. İkinci satır (nil dilim) ile
dördüncü satır (nil sözlük) karşılaştırılırsa fark netleşiyor: ikisi de ilk değeri verilmemiş
ve ikisi de nil. Ama nil dilime append çalışıyor — çıktıda eklenebilir=[1] görünüyor —
nil sözlüğe yazma ise panikliyor. Aynı sözcük (nil), aynı sınıf (tanımlı), iki farklı
sonuç.
DT15. Fark, nilin kendisinde değil, dilin bu iki tip için verdiği ayrı sözde. Bir dilim, nil
olsa bile bir uzunluk (0) ve bir kapasite (0) taşıyan bir başlıktır; append bu başlığa
yeni bir alt dizi ayırıp genişleyebilir, çünkü genişleme dilimin zaten sahip olduğu bir
davranıştır. Bir sözlük ise nil olduğunda hiçbir depolama alanı içermiyor; okuma bu boşlukla
başa çıkabiliyor (aranan anahtar zaten yoktur, sıfır değeri dönülür) ama yazma yeni bir girdi
için yer ayırmayı gerektiriyor ve nil sözlüğün ayıracak hiçbir yeri yok. “Tanımlı” sözcüğü
ikisinde de doğru, ama neyin tanımlı olduğu tipten tipe değişiyor.
Bu, sekiz tanımlı satırı okurken akılda tutulması gereken bir uyarı: bir değerin sıfır değeri olması, o değerin her işlemi kaldırabileceği anlamına gelmiyor. Hangi işlemlerin sıfır değerle çalıştığı, kursun sıradaki konusunda dilim ve sözlük tipleri ayrı ayrı ele alınırken tekrar ölçülecek.
Aynı sınırlayıcı okuma, açıkta kalan üçüncü satırda da bir kez daha görünüyor. İşaretçinin
sıfır değeri de nildir ve nil dilim gibi bazı işlemleri kaldırıyor — bir nil işaretçiyi
başka bir işaretçiyle karşılaştırmak ya da yeniden atamak sorunsuz çalışıyor. Ama alan okumak
(isaretci.Sayi) çalışmıyor, çünkü okunacak bir alan için önce bir yapı örneğinin var olması
gerekiyor ve nil işaretçi hiçbir örneği göstermiyor. Üç tip — dilim, sözlük, işaretçi — üçü
de nili sıfır değeri olarak taşıyor ama üçü de kendi davranış kümesini kendisi tanımlıyor;
tek bir “nil kuralı” yok, tip başına ayrı bir sözleşme var.
Kör Nokta: Redd Nedeni Ölçülmüyor
Dört ret satırı derlenirMinin true/false yanıtına dayanıyor ve hata metni hiç okunmuyor.
Bunun bir bedeli var: bir kaynak, ölçülmek istenenden başka bir nedenle de reddedilmiş
olabilir ve ölçüm bunu ayırt edemez. Bu yüzden kural şu — reddedilen her kaynak parçası, tek
bir yazılmamışlık dışında derlenebilir olmalı; kullanılmayan değişken örneğinde x := 1
dışında hiçbir sorun yok, dönüş değeri örneğinde func F() int { } dışında hiçbir sorun yok.
Bu koşul elle kurulup doğrulandı; ölçüm aracı bunu kendisi denetlemiyor.
DT16. Bu, ölçümün yönteminden gelen bilinçli bir sınırlama, bir gözden kaçırma değil.
derlenirMi yalnız cmd.Run()in çıkış durumunu okuyor; hata metnini okumamak, hem sürüme
bağlı veri yasağını hem de “neden değil, ne” sorusunu koruyor. Bedeli, bu dersteki dört
kaynağın elle doğrulanmış olmasıdır — her biri yazılırken tek bir değişiklik denendi ve
geri kalan her şeyin sabit tutulduğu kontrol edildi. Kursun geri kalanında derleyici reddi
ölçülen her yerde aynı disiplin sürüyor.
Sekiz satırlık tanımlı grubun da benzer bir kör noktası var: gozle işlevi, kendisine verilen
kanıtı olduğu gibi yazdırıyor, doğruluğunu sınamıyor. Bir satırın gerçekten “tanımlı” sınıfına
ait olup olmadığı, kod yazılırken elle karar veriliyor — çerçeve yalnız bu kararı saydırıyor,
kendisi üretmiyor. Kâhin (hangi yazılmamışlığın hangi sınıfa girdiği bilgisi) programın dışında,
bu dersi yazan tarafın elinde.
Özet
- On beş yazılmamışlığın 8’i tanımlı, 4’ü reddedildi, 3’ü açıkta — kursun bağlayıcı dağılımı burada üretildi.
- Go’da “başlatılmamış” diye bir durum yok: sekiz tanımlı satırın her biri, dilin yazılmayanın yerine koyduğu somut bir değer taşıyor.
- Derleyicinin reddettiği dört şey, çalışma zamanında zararsız olsa da yazılmayanın karşılığını daraltma kararıyla derlemeyi durduruyor.
- Tanımlılık okuma yönünde çalışıyor, yazma ve yorumlama yönünde bitiyor:
nilsözlükten okumak tanımlı, yazmak panik; dizginin uzunluğu tanımlı, indekslemesi bayt veriyor. nildilim ilenilsözlük aynı sözcüğü taşısa da aynı işlemleri kaldırmıyor —nildilime eklenebiliyor,nilsözlüğe yazılamıyor.
Sonraki Adım
Sıfır değerler, bir değişkenin hiç dokunulmadığında ne olduğunu ölçtü. Ama bir program
neredeyse hiçbir zaman değişkenlere dokunmadan durmuyor — dallanıyor, döngü kuruyor, bir
durumdan diğerine geçiyor. Sıradaki ders denetim akışına bakıyor: for neden koşulsuz
yazılabiliyor, switch neden ifadesiz yazılabiliyor, ve bir dalın sonunda yazılmayan break
neyin yerini tutuyor?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.