İçeriğe geç
academia.sh

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 ikisi Depolayiciyi karşılıyor, üçü karşılamıyor.
  • Tek yanıtlı biçim (v := deger.(Tip)) başarısız olduğunda panikliyor; panik yalnız recover ile yakalanıp panik=true/panik=false olarak 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 false sayı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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat