İçeriğe geç
academia.sh

Ders 02 / 13

Boş Arayüz ve `any`

Boş arayüz sıfır yöntem istiyor, ve sıfır yöntemi her tip karşılıyor: kaynakta hangi tipin geleceği hiç yazılı değil, tip bilgisi değerin içinde taşınıyor. Aynı değer üç ayrı statik tiple taşındığında dinamik tip değişmiyor; bedeli, değere konan her şeyin çift hâline gelip doğrudan çağrılamaması. Sınırlayıcı ölçüm: nil bir işaretçi boş arayüze konduğunda arayüzün kendisi nil değil.

İçindekiler

Önceki ders arayüz değerinin dinamik tipini bir kez okudu — %T ile main.Dosya — ve üzerinde durmadı. Boş arayüz tam olarak bu satırın kendisi: hiçbir yöntem istemeyen bir arayüz, kayıt sayısıyla değil sıfır sayısıyla tanımlanan bir arayüz. Kaynakta hangi tipin bu arayüze konacağı hiç yazılı olmadığı için, konan tipin bilgisini birinin bir yerde taşıması gerekiyor; önceki dersin bağlayıcı dağılımında o yerin adı zaten geçmişti — değer. Bu ders o satırı büyütüyor.

Önceki dersteki Yazar ve Kapatan gibi arayüzler birer yöntem istiyordu; Gomulu ikisini birden istiyordu. Boş arayüz bu sıralamanın öbür ucunda duruyor: hiç yöntem istemiyor. Bir arayüzün ne kadar az istediği ile onu ne kadar çok tipin karşılayabildiği arasındaki ilişki önceki derste sayıyla ölçülmüştü; bu ders o eğrinin sıfır noktasına odaklanıyor ve orada neyin kazanılıp neyin kaybedildiğini ölçüyor.

Sıfır Yöntem İsteyen Arayüz

Önceki derste yöntem sayısı bir, iki, üçe çıktıkça, aynı dört tipten onu karşılayabilenlerin sayısı üç, iki, bire düşüyordu. Boş arayüz bu eğrinin sıfır ucu: interface{} hiç yöntem istemiyor, ve hiçbir yöntemi olmayan bir koşulu her tip karşılıyor — boş bir küme her kümenin alt kümesidir. Dilin kendi tip adı any, interface{}in kısaltması; bu ders arayüzün kavramına boş arayüz, kod içindeki adına any diyor.

Boş arayüz hiçbir yöntem istemediği için hangi tipin ona konacağını kaynak hiç sınırlamıyor: var v any = 42, var v any = "metin", var v any = Birim{} üçü de derleniyor, çünkü üçünün de karşılaması gereken sıfır yöntem var. Ama arayüzün sıfır yöntem istemesi, konan değerin hiçbir bilgisinin kalmadığı anlamına gelmiyor. Tip bilgisi kaybolmuyor, yalnız görünürlüğü değişiyor — kaynaktan okunabilen bir tip adı olmaktan çıkıp çalışma zamanında sorulması gereken bir bilgiye dönüşüyor.

Bu ders boyunca yazılan her fmt.Println çağrısı da aslında aynı düzeneği kullanıyor: standart kütüphanenin bu işlevi, parametrelerini ...any olarak alıyor. Bir tam sayı, bir dizgi ya da bir yapı — hangisi verilirse verilsin, çağrı sırasında her biri boş arayüze konuyor ve Printlnin içinde %T benzeri bir sorgulamayla geri okunuyor. Boş arayüz bu yüzden nadir görülen bir kenar durum değil; bu dersteki her çıktı satırı zaten ondan geçti.

Aynı Değer, Üç Statik Tip

Bir değerin dinamik tipi, o değerin hangi statik tip üzerinden taşındığından bağımsız. Aşağıdaki ölçüm aynı Birim değerini üç ayrı statik tipin arkasından geçiriyor — kendi arayüzü, sonra boş arayüz, sonra doğrudan boş arayüz — ve her seferinde %T ile dinamik tipi okuyor.

// ayni-deger.go — ayni deger uc ayri statik tipte tasindiginda dinamik tip degismiyor
package main

import "fmt"

type Birim struct{ Ad string }

func (b Birim) Kimlik() string { return b.Ad }

type Tanimlayici interface{ Kimlik() string }

func main() {
	b := Birim{Ad: "birim-42"}

	var t1 Tanimlayici = b
	var t2 any = t1
	var t3 any = b

	fmt.Println("Tanimlayici olarak       :", fmt.Sprintf("%T", t1))
	fmt.Println("any'ye tasindiktan sonra :", fmt.Sprintf("%T", t2))
	fmt.Println("dogrudan any olarak      :", fmt.Sprintf("%T", t3))
}
Tanimlayici olarak       : main.Birim
any'ye tasindiktan sonra : main.Birim
dogrudan any olarak      : main.Birim

Üç değişken üç ayrı statik tip taşıyor — Tanimlayici, sonra any, sonra yine any ama başka bir yoldan gelmiş — ve üçü de aynı yanıtı veriyor: main.Birim. t1den t2ye geçiş bir dönüşüm değil, aynı çiftin (tip, değer) yeniden bir değişkene bağlanması; tip bilgisi kaybolmuyor çünkü hiçbir noktada kaybedilecek bir şey olmuyor, arayüz değeri zaten en baştan beri bu çifti taşıyordu. Tanimlayicinin tek yöntemi de, boş arayüzün sıfır yöntemi de, aynı temel yapıyı görüntülüyor; yalnız görünen yöntem sayısı farklı.

AR4. t3 := b yerine t3 any = b yazılmasının nedeni, Birimin doğrudan boş arayüze konduğu bir yolu da göstermek — t1 ve t2 zincirinin Tanimlayici üzerinden geçtiğini, t3ün geçmediğini ayırt etmek için üçüncü bir satır bilinçli olarak eklendi.

Aynı Kap, Dört Ayrı Dinamik Tip

Önceki ölçüm tek bir değeri üç ayrı statik tipten geçirdi ve dinamik tipin sabit kaldığını gösterdi. Bu ölçüm tersini yapıyor: tek bir statik tip ([]any) içine dört ayrı dinamik tip koyuyor ve her birinin kendi kimliğini koruduğunu gösteriyor.

// karisik.go — ayni any dilimi ic ice farkli dinamik tipler tasiyor
package main

import "fmt"

type Birim struct{ Ad string }

func main() {
	degerler := []any{42, "metin", Birim{Ad: "x"}, 3.14}
	for _, d := range degerler {
		fmt.Printf("deger=%v dinamik tip=%T\n", d, d)
	}
	fmt.Println("dilimin statik tipi: []any, uzunluk:", len(degerler))
}
deger=42 dinamik tip=int
deger=metin dinamik tip=string
deger={x} dinamik tip=main.Birim
deger=3.14 dinamik tip=float64
dilimin statik tipi: []any, uzunluk: 4

AR5. Dizi bilinçli olarak dört ayrı temel kategoriden seçildi — bir tam sayı, bir dizgi, bir yapı, bir kayan noktalı sayı — ki gösterim tek bir tip ailesine (örneğin yalnız sayısal tipler) özgü bir davranış değil, boş arayüzün genel kuralı olduğu görülebilsin.

Dilimin kendisi tek bir statik tip taşıyor: []any. Ama dört ögesi dört ayrı dinamik tip taşıyor — int, string, main.Birim, float64 — ve range döngüsü bunların hiçbirini birbirine karıştırmıyor, her öge kendi çiftini kendisiyle birlikte getiriyor. Bu, önceki ölçümün aynasıdır: orada aynı tip üç statik biçimde sabit kaldı, burada aynı statik biçim dört ayrı tipi taşıyor. İkisi birlikte tip bilgisinin nerede durduğunu netleştiriyor — statik tipte değil, her değerin kendi taşıdığı çiftte.

Bu tür heterojen bir kap — tek bir dilimde birbirinden farklı tipte değerler tutmak — tip parametreleriyle yazılan genel veri yapılarından önce yalnız boş arayüzle kurulabiliyordu. İki yazımın nerede ayrıştığı kursun genellikler bölümünde ölçülecek; burada yalnız boş arayüzün bunu nasıl yaptığı önemli: her ögeyi kendi çiftiyle birlikte saklayarak.

Bedel: Çift Olmak, Doğrudan Çağrılamamak

Tip bilgisinin değerde taşınmasının bir bedeli var: boş arayüze konan bir değer, kendi yöntemleriyle doğrudan çağrılamıyor. any sıfır yöntem istediği için sıfır yöntem sunuyor — üzerinde tanımlı hiçbir yöntem yok, konan değerin kendi yöntemleri olsa bile. Çağırmak için önce tip iddiasıyla asıl tipe (ya da asıl tipin karşıladığı bir arayüze) dönmek gerekiyor.

Bu, önceki dersteki küçük arayüz ölçümünün simetrik karşılığı. Orada yöntem sayısı arttıkça onu karşılayan tip sayısı azalıyordu; burada yöntem sayısı sıfıra indiğinde erişim de sıfırlanıyor. Boş arayüz her tipi kabul ediyor çünkü hiçbir şey istemiyor, ve hiçbir şey istemediği için hiçbir şey de sunmuyor — kabul genişliği ile doğrudan erişim ters orantılı. Derleyici burada bir tahmin yürütmüyor: vnin statik tipi any olduğu sürece, v üzerinde hangi yöntemlerin bulunduğunu bilmesinin hiçbir yolu yok, çünkü bu bilgi ancak çalışma zamanında değerin kendisine bakılarak öğrenilebilir.

// arac.go — bir kaynak parcasinin derlenip derlenmedigini programin icinden sinar
package main

import (
	"os"
	"os/exec"
	"path/filepath"
)

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
}

Bu blok yalnız diske yazılıyor; iki kaynak parçasını üretip derleyiciyi çağıran asıl ölçüm aşağıda.

// bedel.go — any uzerinden dogrudan cagri reddediliyor, tip iddiasindan sonraki cagri gecerli
package main

import "fmt"

func main() {
	ortak := `package sinama

type Birim struct{ Ad string }

func (b Birim) Kimlik() string { return b.Ad }

type Tanimlayici interface{ Kimlik() string }

func F() string {
	var v any = Birim{Ad: "x"}
`
	dogrudanCagri := ortak + `	return v.Kimlik()
}
`
	iddiaylaCagri := ortak + `	return v.(Tanimlayici).Kimlik()
}
`
	fmt.Println("dogrudan cagri, any uzerinden  :", derlenirMi(dogrudanCagri))
	fmt.Println("tip iddiasiyla cagri           :", derlenirMi(iddiaylaCagri))
}
dogrudan cagri, any uzerinden  : false
tip iddiasiyla cagri           : true

v.Kimlik() derlenmiyor, çünkü vnin statik tipi any ve anynin yöntem kümesi boş; derleyici Kimlik adlı bir yöntemi any üzerinde aramıyor bile, çünkü orada aranacak hiçbir yöntem tanımlı değil. v.(Tanimlayici).Kimlik() derleniyor, çünkü tip iddiası önce değeri Tanimlayiciye çeviriyor ve Tanimlayicinin (dolayısıyla Kimlikin) yöntem kümesi üzerinden çağrı yapılabiliyor. Bedel şu: any bir değeri her tipten kabul ediyor, ama karşılığında o değerin kendi davranışına doğrudan erişimi kapatıyor. Tip iddiasının kendisi sıradaki dersin konusu; burada yalnız onun gerekliliği ölçülüyor.

Bu ret bir eksiklik değil, bilinçli bir sınır. Derleyici v.Kimlik() satırını görürken elinde yalnız vnin statik tipi (any) var; hangi somut değerin orada duracağı derleme zamanında henüz bilinmiyor, ilk çalıştığında belirleniyor. Derleyici bu boşluğu “muhtemelen Kimlik vardır” diye tahmin ederek doldurmuyor — böyle bir tahmin, yanlış çıktığında ancak çalışma zamanında, belki de üretim ortamında patlardı. Bunun yerine derleyici, statik tipin sunduğu yöntem kümesinin dışına hiç çıkmıyor; boş arayüz için bu küme boş olduğundan, çağrı orada duruyor. Tip iddiası bu boşluğu açıkça kapatan tek yol.

Sınırlayıcı Ölçüm: Nil İşaretçi Taşıyan Arayüz Nil Değildir

Boş arayüzün en tuhaf satırı, tip bilgisinin bedelsiz taşınmadığını gösteriyor. Bir işaretçi nil olabilir; aynı işaretçi boş arayüze konduğunda, arayüzün kendisi nil olmuyor.

// nil-arayuz.go — isaretci nil olsa da onu tasiyan arayuz nil degil
package main

import "fmt"

type Sunucu struct{}

func main() {
	var s *Sunucu
	var v1 any = s
	var v2 any

	fmt.Println("isaretcinin kendisi nil mi  :", s == nil)
	fmt.Println("isaretciyi tasiyan v1 nil mi:", v1 == nil)
	fmt.Println("hic deger konmayan v2 nil mi:", v2 == nil)
}
isaretcinin kendisi nil mi  : true
isaretciyi tasiyan v1 nil mi: false
hic deger konmayan v2 nil mi: true

Üç satır bilinçli olarak yan yana konuldu. s gerçekten nil — hiçbir Sunucu örneğini göstermiyor. v1 bu nil işaretçiyi taşıyor, ama v1 == nil false veriyor: arayüz değeri bir çift, ve bu çiftin tip yuvası dolu — *main.Sunucu orada duruyor — yalnız değer yuvası nil. Çift yalnız her iki yuvası da boşken nil sayılıyor; v2 hiç değer almadığı için hem tip hem değer yuvası boş kalıyor ve gerçekten nil.

Çiftin tip yuvasının gerçekten ayrı bir bilgi taşıdığı, iki farklı işaretçi tipi yan yana konduğunda daha da netleşiyor.

// iki-nil.go — iki ayri isaretci tipinin nil degerleri birbirine esit sayilmiyor
package main

import "fmt"

type A struct{}
type B struct{}

func main() {
	var a *A
	var b *B
	var v1 any = a
	var v2 any = b

	fmt.Println("v1 nil mi       :", v1 == nil)
	fmt.Println("v2 nil mi       :", v2 == nil)
	fmt.Println("v1 v2'ye esit mi:", v1 == v2)
	fmt.Printf("v1 dinamik tip=%T, v2 dinamik tip=%T\n", v1, v2)
}
v1 nil mi       : false
v2 nil mi       : false
v1 v2'ye esit mi: false
v1 dinamik tip=*main.A, v2 dinamik tip=*main.B

a ve b ikisi de nil işaretçi ve ikisi de sıfır alanlı, görünüşte “boş” birer yapıyı gösteriyor. Ama v1 ile v2 birbirine eşit değil, çünkü arayüz karşılaştırması önce tip yuvasını karşılaştırıyor ve *main.A ile *main.B aynı tip değil. İki değer yuvası da nil olsa bile, tip yuvaları farklı olduğu için çift olarak eşit sayılmıyorlar. Bu, tip yuvasının değer yuvasından bağımsız, gerçek bir bilgi taşıdığının bir başka kanıtı — yalnız “dolu mu boş mu” değil, “hangi tip” sorusunun da karşılaştırmaya girdiğini gösteriyor.

AR6. Bu, önceki dersin birinci iddiasının doğrudan sonucu: bağların sekizi değerin içinde taşınıyorsa, değerin boş olması çiftin boş olması demek değil. Go Temelleri kursunun Sıfır Değerler dersi nil dilime eklenebilirken nil sözlüğe yazılamadığını ölçmüştü; bu satır aynı ailenin üçüncü örneği — nil sözcüğü aynı kalıyor, taşıdığı anlam tipten tipe değişiyor.

Bu satırın pratik sonucu şu: bir fonksiyon error (kendisi bir arayüz) döndürüyorsa ve içeride somut bir işaretçi tipi kullanıyorsa, o işaretçi nil olsa bile döndürülen hata değeri nil olmayabilir. Çağıran taraf hata == nil diye sorduğunda false yanıtını alır, çünkü sorduğu şey işaretçinin kendisi değil, onu taşıyan çifttir. Bu tuzağın tam ölçümü hata değerlerine ayrılan derste yapılacak; burada yalnız kökeni gösterildi.

Kökeni bu kadar erken görmenin bir nedeni var: hata, kendisi de bir arayüz, ve arayüz olan her şey aynı çift kuralına tabi. error boş arayüz değil — Error() string yöntemini istiyor — ama boş arayüzle paylaştığı şey tam olarak bu ölçümün konusu: ikisi de bir tip yuvası ve bir değer yuvası taşıyan bir çift. Boş arayüzde bu çifti doğrudan gözlemlemek kolay, çünkü hiçbir yöntem araya girmiyor; hata arayüzünde aynı çift bir yöntem imzasının arkasına gizleniyor ama davranışı değişmiyor.

Özet

  • Boş arayüz sıfır yöntem istiyor ve sıfır yöntemi her tip karşılıyor; any bu arayüzün kod içindeki adı.
  • Aynı değer üç ayrı statik tiple (özel arayüz, ondan anyye, doğrudan any) taşındığında dinamik tip değişmiyor — tip bilgisi kaybolmuyor, yalnız görünürlüğü değişiyor.
  • Boş arayüze konan bir değer doğrudan çağrılamıyor; anynin yöntem kümesi boş olduğu için çağrı önce bir tip iddiası gerektiriyor.
  • Nil bir işaretçi boş arayüze konduğunda arayüzün kendisi nil değil — çift yalnız tip ve değer yuvası birlikte boşken nil sayılıyor.
  • İki ayrı işaretçi tipinin nil değerleri boş arayüze konduğunda birbirine eşit sayılmıyor; karşılaştırma değer yuvasından önce tip yuvasına bakıyor.

Sonraki Adım

Bu derste tip bilgisine yalnız %T ile, salt okuma amacıyla erişildi — hiçbir satır vnin gerçek değerini geri almadı, yalnız hangi tipte olduğunu okudu. Ama bir programın asıl sorması gereken soru genellikle “bu değerin tipi ne” değil, “bu değer şu tipi karşılıyor mu” ve karşılıyorsa “asıl değeri bana ver”. Boş arayüzden çıkışta zaten bir kez görüldü: v.(Tanimlayici) değeri geri kazandırdı ama yanıtı denetlemeden. Sıradaki ders bu iki soruyu ayrı ayrı ölçüyor: tip iddiasının iki yanıtlı biçimi ve tip anahtarının seçtiği dal, değere sormanın iki ayrı yolu.

İ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