Ders 03 / 13
Tip İddiası ve Tip Anahtarı
Beş değerden ikisi bir arayüzü karşılıyor; tip iddiasının iki yanıtlı biçimi bunu panik olmadan sayıyor, tek yanıtlı biçim başarısız olduğunda panikliyor ve panik yalnız panik=true olarak okunuyor. Tip anahtarı aynı beş değeri kendi dallarına dağıtıyor. Sınırlayıcı ölçüm: aynı değer hem bir somut tip dalına hem bir arayüz dalına uyabiliyor, ve seçilen dal yazılış sırasına bağlı — ders bunu iki sıralamayla koşturuyor.
İçindekiler
Önceki ders boş arayüzün bir değerin tip bilgisini kaybetmeden taşıdığını ölçtü, ama o
bilgiye yalnız %T ile, salt okuma amacıyla baktı. Bu bilginin gerçek kullanımı okumak değil
sormaktır: bu değer şu arayüzü karşılıyor mu, ve karşılıyorsa asıl değeri bana ver. Go bu
soruyu iki ayrı yapıyla soruyor — tip iddiası tek bir aday hakkında soruyor, tip
anahtarı birden çok aday arasından seçim yapıyor. İkisi de değere soruyor, ikisi de aynı
çiftin (tip, değer) içine bakıyor.
İkisinin de sorduğu şey, ilk dersteki on altı bağın en sık tekrarlanan kalıbı: bir bağ kaynakta yazılı değilse ve derleyici onu kullanım anında kurmuyorsa, geriye tek yol kalıyor — çalışma zamanında değerin kendisine sormak. Bu ders o sormanın iki biçimini, sonuçlarını ve bir tanesinin taşıdığı riski ölçüyor.
Tip İddiasının İki Yanıtlı Biçimi
Bir tip iddiası deger.(Tip) biçiminde yazılıyor ve iki biçimi var. Tek yanıtlı biçim
(v := deger.(Tip)) yalnız değeri döndürüyor; iddia yanlışsa çalışma zamanı hatası veriyor.
İki yanıtlı biçim (v, tamam := deger.(Tip)) ikinci bir mantıksal değer döndürüyor;
iddia yanlışsa tamam false oluyor ve program durmuyor.
Tip yerine yazılan şey iki ayrı soru sorabiliyor. Somut bir tip yazıldığında (“bu değerin
dinamik tipi tam olarak bu mu”) karşılaştırma dinamik tipin kendisine bakıyor. Bir arayüz
yazıldığında (“bu değer bu sözleşmeyi karşılıyor mu”) karşılaştırma yöntem kümesine bakıyor.
İkisi de aynı deger.(Tip) yazımını paylaşıyor; hangi soru sorulduğu yalnız Tipin kendi
doğasından, somut mu arayüz mü olduğundan, anlaşılıyor. Aşağıdaki ölçüm bir arayüz sorusu
soruyor: beş ayrı değeri aynı arayüze karşı sınıyor ve kaçının karşıladığını sayıyor.
// bes-soru.go — bes degerden kaci Depolayici arayuzunu karsiliyor
package main
import "fmt"
type Depolayici interface{ Depola() string }
type DiskKaydi struct{}
func (DiskKaydi) Depola() string { return "disk" }
type BellekKaydi struct{}
func (BellekKaydi) Depola() string { return "bellek" }
type Gecici struct{}
func main() {
degerler := []any{DiskKaydi{}, 42, BellekKaydi{}, "metin", Gecici{}}
dogruSayisi := 0
for i, d := range degerler {
depo, tamam := d.(Depolayici)
if tamam {
fmt.Printf("deger#%d dinamik_tip=%T Depolayici_mi=%t cagri=%s\n", i, d, tamam, depo.Depola())
dogruSayisi++
continue
}
fmt.Printf("deger#%d dinamik_tip=%T Depolayici_mi=%t\n", i, d, tamam)
}
fmt.Println("bes sorudan dogru sayisi:", dogruSayisi)
}
deger#0 dinamik_tip=main.DiskKaydi Depolayici_mi=true cagri=disk deger#1 dinamik_tip=int Depolayici_mi=false deger#2 dinamik_tip=main.BellekKaydi Depolayici_mi=true cagri=bellek deger#3 dinamik_tip=string Depolayici_mi=false deger#4 dinamik_tip=main.Gecici Depolayici_mi=false bes sorudan dogru sayisi: 2
Beş değerden ikisi (DiskKaydi, BellekKaydi) Depolayiciyi karşılıyor, üçü (int,
string, Gecici) karşılamıyor. Gecici bir yapı olduğu hâlde karşılamıyor, çünkü Depola
yöntemini hiç yazmamış — arayüz karşılamasının koşulu yapı olmak değil, yöntem kümesinin
eşleşmesi. tamam değişkeni bu beş sorunun her birine kesin bir yanıt veriyor ve döngü hiçbir
noktada durmuyor; bu, iki yanıtlı biçimin asıl gücü.
depo değişkeni bu ölçümün ikinci yarısını taşıyor. Tip iddiası yalnız “karşılıyor mu”
sorusuna yanıt vermiyor; tamam doğruysa depo artık statik tipi Depolayici olan,
Depola yöntemi çağrılabilir gerçek bir değer. cagri=disk ve cagri=bellek sütunları bu
değerin gerçekten kullanılabilir olduğunu gösteriyor — önceki dersteki “doğrudan
çağrılamaz” bedeli burada tam olarak geri ödeniyor: tip iddiası başarılı olduğunda değer,
boş arayüzün kapattığı erişimi yeniden açıyor.
AR7. Beş değer bilinçli olarak üç farklı başarısızlık nedeninden seçildi: int ve
string hiç yöntem taşımıyor, Gecici bir yapı ama yanlış (boş) yöntem kümesine sahip.
Üçü de aynı false yanıtını veriyor ama nedenleri farklı — ölçüm bu farkı ayırt etmiyor,
yalnız karşılayıp karşılamadığını sayıyor.
İki yanıtlı biçim başarısız olduğunda ilk değişken (depo) rastgele bir şey değil, hedef
tipin kendi sıfır değeri oluyor — burada Depolayici bir arayüz olduğu için sıfır değeri
nil. Bu, önceki iki dersin bağladığı bir noktayı bir kez daha doğruluyor: Go’da yazılmayan
ya da başarısız olan bir şeyin karşılığı asla tanımsız kalmıyor, her zaman kendi tipinin
sıfır değeri devreye giriyor.
Tek Yanıtlı Biçim Başarısız Olduğunda: Panik
İki yanıtlı biçim başarısızlığı bir değere çeviriyor; tek yanıtlı biçim aynı başarısızlığı bir panige çeviriyor. Aşağıdaki ölçüm bunu doğrudan koşturuyor.
// panik-olcumu.go — tek yanitli tip iddiasi basarisiz oldugunda panikliyor
package main
import "fmt"
type Depolayici interface{ Depola() string }
type DiskKaydi struct{}
func (DiskKaydi) Depola() string { return "disk" }
func panikliyorMu(f func()) (p bool) {
defer func() {
if recover() != nil {
p = true
}
}()
f()
return false
}
func main() {
var basarili any = DiskKaydi{}
var basarisiz any = 42
fmt.Println("basarili tek yanitli panikliyor mu :", panikliyorMu(func() {
_ = basarili.(Depolayici)
}))
fmt.Println("basarisiz tek yanitli panikliyor mu :", panikliyorMu(func() {
_ = basarisiz.(Depolayici)
}))
fmt.Println("basarisiz iki yanitli panikliyor mu :", panikliyorMu(func() {
_, tamam := basarisiz.(Depolayici)
_ = tamam
}))
}
basarili tek yanitli panikliyor mu : false basarisiz tek yanitli panikliyor mu : true basarisiz iki yanitli panikliyor mu : false
Üç satır aynı basarisiz değeri (bir int) sınıyor, yalnız çağrı biçimi değişiyor. Tek
yanıtlı biçim DiskKaydi{} için panik üretmiyor, çünkü iddia gerçekten doğru. Aynı biçim
42 için panikliyor, çünkü int Depolayiciyi karşılamıyor ve tek yanıtlı biçim bu
durumda çalışma zamanı hatası fırlatıyor — panikliyorMu bu hatayı recover ile yakalayıp
yalnız panik=true olarak okuyor, yığıt izi hiç basılmıyor. Üçüncü satır aynı başarısız
iddiayı iki yanıtlı biçimle deniyor ve panik hiç oluşmuyor; tamam sessizce false oluyor.
Fark biçimde: aynı soru, iki ayrı sonuç sözleşmesi.
AR8. panikliyorMu burada Go Temelleri kursunun eksik.go çerçevesinden aynı işlevle
yeniden kuruldu: bir işlevi çalıştırıp panikleyip paniklemediğini recover ile yakalıyor ve
yalnız iki değerden birini döndürüyor. Bu, kursun panik ölçen her yerinde geçerli tek yöntem.
Bu iki değerlik örnek tek başına yeterli değil: beş değerin tamamı tek yanıtlı biçimden
geçirilirse, panikleyenlerin sayısı önceki bölümdeki false sayısıyla aynı çıkmalı — ikisi
aynı olguyu iki ayrı sözleşmeden okuyor.
// panik-sayimi.go — bes degerin kaci tek yanitli bicimde panikliyor
package main
import "fmt"
type Depolayici interface{ Depola() string }
type DiskKaydi struct{}
func (DiskKaydi) Depola() string { return "disk" }
type BellekKaydi struct{}
func (BellekKaydi) Depola() string { return "bellek" }
type Gecici struct{}
func panikliyorMu(f func()) (p bool) {
defer func() {
if recover() != nil {
p = true
}
}()
f()
return false
}
func main() {
degerler := []any{DiskKaydi{}, 42, BellekKaydi{}, "metin", Gecici{}}
panikSayisi := 0
for i, d := range degerler {
panikledi := panikliyorMu(func() {
_ = d.(Depolayici)
})
fmt.Printf("deger#%d dinamik_tip=%T panikledi=%t\n", i, d, panikledi)
if panikledi {
panikSayisi++
}
}
fmt.Println("bes degerden panikleyen sayisi:", panikSayisi)
}
deger#0 dinamik_tip=main.DiskKaydi panikledi=false deger#1 dinamik_tip=int panikledi=true deger#2 dinamik_tip=main.BellekKaydi panikledi=false deger#3 dinamik_tip=string panikledi=true deger#4 dinamik_tip=main.Gecici panikledi=true bes degerden panikleyen sayisi: 3
Panikleyen sayısı üç, ilk ölçümdeki dogruSayisinin (iki) tam tamamlayıcısı — beş değerin
ikisi karşılıyordu, üçü karşılamıyordu, ve şimdi o üçü tek yanıtlı biçimde panikliyor. İki
ölçüm aynı beş değeri, aynı arayüze karşı, iki ayrı çağrı biçimiyle soruyor ve toplam beşe
tamamlanan iki sayı üretiyor. Bu, iki biçimin aynı bilgiyi taşıdığının bir kanıtı: fark
bilgide değil, başarısızlığın nasıl bildirildiğinde.
Tip Anahtarının Seçtiği Dal
Tip anahtarı, birden fazla aday arasından tek bir dal seçiyor. switch v.(type) yazımı her
casei sırayla deniyor ve eşleşen ilk daldan devam ediyor. Go Temelleri kursunun Denetim
Akışı dersinde ölçülen kendiliğinden düşmeme kuralı burada da geçerli: eşleşen dal bir
sonraki casee düşmüyor, fallthrough yazılmadığı sürece dal kendi başına bitiyor. Aynı
beş değer bu kez dört dallı bir tip anahtarından geçiyor.
// dal-sec.go — tip anahtari bes degeri kendi dallarina dagitiyor
package main
import "fmt"
type Depolayici interface{ Depola() string }
type DiskKaydi struct{}
func (DiskKaydi) Depola() string { return "disk" }
type BellekKaydi struct{}
func (BellekKaydi) Depola() string { return "bellek" }
type Gecici struct{}
func dalSec(v any) string {
switch t := v.(type) {
case int:
return fmt.Sprintf("tam sayi dali: %d", t)
case string:
return fmt.Sprintf("dizgi dali: %q", t)
case DiskKaydi:
return "DiskKaydi dali"
case BellekKaydi:
return "BellekKaydi dali"
default:
return "varsayilan dal"
}
}
func main() {
degerler := []any{DiskKaydi{}, 42, BellekKaydi{}, "metin", Gecici{}}
for i, d := range degerler {
fmt.Printf("deger#%d -> %s\n", i, dalSec(d))
}
}
deger#0 -> DiskKaydi dali deger#1 -> tam sayi dali: 42 deger#2 -> BellekKaydi dali deger#3 -> dizgi dali: "metin" deger#4 -> varsayilan dal
Beş değerin dördü kendi somut tipine ayrılmış bir dala düşüyor; Gecici hiçbir casee
uymadığı için defaulte düşüyor. t := v.(type) yazımındaki t, her dalın içinde o dala
özgü statik tipte bir değişken — case int dalında t bir int, case string dalında bir
string. Bu, tip anahtarının tip iddiasından farkı: tip iddiası tek bir tipi soruyor, tip
anahtarı aynı sorguyu birden çok adaya karşı tek seferde yürütüp hangisinin eşleştiğini
söylüyor.
Aynı sonucu beş ayrı tip iddiasıyla da elde etmek mümkündü — her aday için bir if d, tamam := v.(X); tamam satırı yazıp ilk doğru olanı seçmek. Tip anahtarı bunu tek bir yapıda
topluyor ve tnin dala özgü statik tipini otomatik olarak sağlıyor; beş ayrı iddiada bu
tipi her dalda elle yeniden yazmak gerekirdi. Kazanılan şey bir yeni yetenek değil, aynı
sorgunun tek bir yerde toplanması.
Sınırlayıcı Ölçüm: Seçilen Dal Yazılış Sırasına Bağlı
Tip anahtarı yalnız somut tipleri değil, arayüzleri de dal olarak kabul ediyor. Bir değer hem bir somut tip dalına hem bir arayüz dalına uyabiliyorsa, hangisinin seçileceği dalların yazılış sırasına bağlı — ilk eşleşen dal kazanıyor.
Önceki bölümdeki dört dal (int, string, DiskKaydi, BellekKaydi) birbirini dışlıyordu:
tek bir değer bu dallardan yalnız birine uyabiliyordu. Ama bir somut tip ile o tipin
karşıladığı bir arayüz birbirini dışlamıyor — DiskKaydi hem DiskKaydinin kendisi hem
Depolayici demek. İki case aynı değeri talep ettiğinde tip anahtarı bir çakışma hatası
vermiyor, sessizce ilk yazılanı seçiyor.
// sira-bagimliligi.go — ayni deger, dallarin sirasina gore iki ayri sonuc veriyor
package main
import "fmt"
type Depolayici interface{ Depola() string }
type DiskKaydi struct{}
func (DiskKaydi) Depola() string { return "disk" }
func siniflaSomutOnce(v any) string {
switch v.(type) {
case DiskKaydi:
return "somut: DiskKaydi"
case Depolayici:
return "arayuz: Depolayici"
default:
return "bilinmeyen"
}
}
func siniflaArayuzOnce(v any) string {
switch v.(type) {
case Depolayici:
return "arayuz: Depolayici"
case DiskKaydi:
return "somut: DiskKaydi"
default:
return "bilinmeyen"
}
}
func main() {
v := DiskKaydi{}
fmt.Println("somut dal once yazilirsa :", siniflaSomutOnce(v))
fmt.Println("arayuz dali once yazilirsa:", siniflaArayuzOnce(v))
}
somut dal once yazilirsa : somut: DiskKaydi arayuz dali once yazilirsa: arayuz: Depolayici
DiskKaydi{} iki dala da uyuyor: kendi somut tipine ve Depolayiciye, çünkü Depola
yöntemini karşılıyor. siniflaSomutOnce DiskKaydi dalını önce yazdığı için o dalı
seçiyor; siniflaArayuzOnce aynı değeri, yalnız dalların sırası tersine çevrildiği için,
Depolayici dalına düşürüyor. Kaynak metin iki işlevde de aynı iki casei taşıyor,
yalnız sırası değişmiş; sonuç bambaşka. Bu, önceki dersteki küçük arayüz ölçümüyle aynı
gerçeğe farklı bir açıdan bakıyor: bir değer birden çok arayüzü aynı anda karşılayabiliyor,
ve tip anahtarı bu çokluğu ilk yazılan dalı seçerek çözüyor.
AR9. Bu sıra bağımlılığı yalnız somut tip ile arayüz dalı arasında değil, iki ayrı arayüz dalı arasında da geçerli — bir değer iki arayüzü birden karşılıyorsa, hangi arayüz dalının önce yazıldığı yine kazanıyor. Ölçüm burada tek bir çifti (somut tip, arayüz) sınıyor çünkü bu, kaynakta en sık karşılaşılan biçim; ilke ikiden fazla dal için de aynı kalıyor.
Bunun sonucu, normal bir switchteki alışılmış sezgiyle çelişiyor: sıradan bir switchte
dalların sırası genellikle yalnız okunabilirliği etkiler, sonucu değiştirmez, çünkü her dal
birbirini dışlayan ayrı bir değeri karşılaştırır. Tip anahtarında dallar birbirini
dışlamayabilir — bir değer aynı anda birden fazla dala uyabilir — ve bu durumda sıra artık
salt bir okunabilirlik tercihi değil, hangi dalın çalışacağını belirleyen bir karardır.
Ölçümün gösterdiği şey tam olarak bu: iki işlev aynı iki casei taşıyor, aynı değeri
alıyor, ve yalnız bir satırın yerinin değişmesiyle iki ayrı yanıt üretiyor.
Özet
- Tip iddiasının iki yanıtlı biçimi (
v, tamam := deger.(Tip)) başarısızlığı bir mantıksal değere çeviriyor; beş değerden ikisiDepolayiciyi karşılıyor, üçü karşılamıyor. - Tek yanıtlı biçim (
v := deger.(Tip)) başarısız olduğunda panikliyor; panik yalnızrecoverile yakalanıppanik=true/panik=falseolarak okunuyor. - Tip anahtarı aynı sorguyu birden çok adaya karşı tek seferde yürütüyor ve eşleşen ilk daldan devam ediyor, bir sonraki dala düşmüyor.
- Bir değer hem bir somut tip dalına hem bir arayüz dalına uyabiliyor; seçilen dal kaynağın yazılış sırasına bağlı, değerin kendisine değil.
- İki yanıtlı biçimin panik üretmemesi ile tek yanıtlı biçimin panik sayısının (üç) iki
yanıtlı biçimin
falsesayısını (üç) tam karşılaması, iki çağrı biçiminin aynı bilgiyi taşıdığını, yalnız başarısızlığı farklı bildirdiğini gösteriyor. - Tip iddiası başarılı olduğunda geri dönen değer gerçekten çağrılabilir; önceki dersteki “doğrudan çağrılamaz” bedeli, başarılı bir iddiadan sonra geri ödeniyor.
Sonraki Adım
Bu derste Depolayici tek bir yöntem istiyordu ve tek bir sözleşme kuruyordu. Ama bir tip
birden çok sözleşmeyi aynı anda karşılamak zorunda kaldığında — hem depolayabilmeli hem
kapatılabilmeli — bu iki sözleşmeyi kaynakta birleştirmenin bir yolu gerekiyor. Sınırlayıcı
ölçümdeki Depolayici dalı da böyle bir birleşmenin habercisiydi: DiskKaydi yalnız
Depola yazdı ama başka sözleşmeleri de karşılayabilirdi. Sıradaki ders bu birleşmeyi
ölçüyor: gömülü arayüzler bir sözleşmeyi kaynakta nasıl kuruyor, ve bu birleşme
gerçekleştirime hiç yeni yöntem eklemeden nasıl oluyor?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.