İçeriğe geç
academia.sh

Ders 15 / 18

Paket Düzeni ve Görünürlük

Dışa aktarımın bir paketten diğerine görünür olup olmadığını tek bir harfin büyüklüğüne bağladığı, derleyici program içinden çağrılarak dört denemeyle gösterilir: büyük harfli bir işlev ve yalnız dışa aktarılan alanı kurulan bir yapı derlenir; küçük harfli işlev ve yapının dışa aktarılmayan alanı başka paketten okunmaya çalışıldığında derlenmez. Sınırlayıcı ölçüm, aynı paketin iki ayrı dosyasının birbirinin küçük harfli adını sorunsuz paylaştığını gösterir — görünürlüğün sınırı dosya değil pakettir.

İçindekiler

Önceki dersin kapanışı bir dizginin ne olduğunu dilin tip sisteminin söylediğini ama ne anlama geldiğini söylemediğini kurdu. Bu ders aynı soruyu kaynağın dışına taşıyor: bir adın görünürlüğünü, bir sürümün seçilmesini ve bir kuralın uygulanmasını kim belirliyor? İlk durak görünürlük. Go’da bir tanımlayıcıyı başka bir pakete açık kılmak için yazılacak bir anahtar sözcük yoktur — public, private ya da benzeri bir bildirim yoktur. Yazılan tek şey addır, ve adın ilk harfinin büyük ya da küçük olması bütün kararı verir. Bu dersin sorusu kursun genel sorusunun aynısı: bunu yazmadığında, yani harfi büyük yazmadığında, ne oluyor?

Bu sorunun kursun genel çerçevesindeki yeri biraz farklı. Önceki konuda “yazılmayan” şey hep bir değerdi — ilk değeri verilmeyen bir değişken, kullanılmayan bir dönüş. Burada yazılmayan şey bir erişim kararı: bir adı büyük harfle yazmamak. Yanıt da konuma bağlı hâle geliyor — aynı küçük harfli ad, kendi paketinin içinde tanımlı bir biçimde çalışıyor, paketin dışından çağrıldığında reddediliyor. Kursun geri kalanında tek bir sınıf tek bir sonuca karşılık gelirken, bu derste aynı yazılmamışlığın karşılığı nereden bakıldığına göre değişiyor; ölçüm de bu yüzden aynı adı iki farklı konumdan çağırarak kuruluyor.

Dışa Aktarmanın Kuralı: Bir Harf

PK1. Go, görünürlük için ayrı bir bildirim sözdizimi taşımaz; bir tanımlayıcının paket dışından görünür olup olmadığı yalnız adın ilk harfinin büyük (dışa aktarılan) ya da küçük (dışa aktarılmayan) olmasıyla belirlenir. PK2. Bu kural tek bir tanımlayıcı türüne özgü değildir — işlev, tip, değişken, sabit ve bir yapının alanı aynı harfe bakan aynı kuraldan geçer.

Bu, önceki derste kurulan sıfır değer kuralına benzer bir tasarım kararı: dil, yazılmayan bir şeyin karşılığını kendisi belirliyor. Aradaki fark yönde: sıfır değer, yazılmayan bir değeri dolduruyordu; dışa aktarma kuralı, yazılmayan bir erişim bildirimini dolduruyor — ve harfin büyüklüğü dışında hiçbir şey yazılmadığında derleyici bunu daima “dışa aktarılmıyor” olarak okur. Kararın kendisi bir seçim değil, adın yazılış biçiminin zorunlu bir sonucu.

Bu tasarımın gerekçesi bir kısayoldan ibaret değil. Ayrı bir görünürlük bildirimi (public/private gibi) her tanımlayıcı için ikinci bir karar ister: adı seç, sonra görünürlüğü seç. Go bu ikinci kararı adın kendisine gömerek kaldırıyor — bir tanımlayıcının görünürlüğünü değiştirmek, adını değiştirmek demektir, ve bu ikisi hiçbir zaman birbirinden ayrışamaz. Kararın bir sonucu da şu: görünürlük bir çalışma zamanı denetimi değil, tamamen derleme anında çözülen bir sözdizim kuralı. Aşağıdaki ölçüm bunu tam olarak bu yüzden ikinci bir çalıştırma yapmadan, yalnız derleyiciyi çağırarak sınıyor — görünürlük ihlali bir istisna ya da hata değeri olarak değil, derlemenin kendisinin durması olarak ortaya çıkıyor.

Bir Harfin Kapsadığı Alan

Aynı kural, üç dersten beri kurulan başka tanımlayıcı türlerine de sessizce uygulanıyor. Önceki konuda iota ile sıralanan sabitler büyük harfle yazılırsa dışa aktarılır, küçük harfle yazılırsa yalnız kendi paketinde kalır — sabitin değeri için ayrı bir kural yoktur, yalnız adı için aynı kural işler. Bir yapıya bağlı bir yöntemin adı da aynı sınavdan geçer: alıcısı (bileşik tipler konusunda kurulan kavram) dışa aktarılan bir tip olsa bile, yöntemin kendi adı küçük harfle başlıyorsa o yöntem yalnız kendi paketinden çağrılabilir. Bir paketin dışarıya sunduğu şey bu yüzden tek tek adlara bakılarak okunabilir: paketin kaynağını tarayan biri, hangi işlevin, tipin, sabitin ya da yöntemin dışarıdan erişilebilir olduğunu görmek için ayrı bir bildirim listesi aramaz, yalnız ilk harflere bakar. Bu okunabilirlik bir yan etki değil, kuralın asıl amacı: bir paketin genel yüzeyi, kaynağın kendisinden başka hiçbir belgeye bakmadan çıkarılabilir bir bilgi hâline geliyor.

Derleyiciyi İki Paketli Bir Kurguyla Çağırmak

Kursun ilk dersinden beri süren yordam burada genişliyor: derleyici, kaynağı gerçekten derleyerek sınanıyor, hata metni hiç okunmuyor, yalnız derlendi/derlenmedi sonucu alınıyor. O dersteki kurgu tek bir paketi sınıyordu; bu ders iki paketli bir kurgu kuruyor, çünkü görünürlüğün asıl sınandığı yer paket sınırının kendisi. Aşağıdaki yordamlar bir kütüphane paketini ayrı bir alt dizine, onu çağıran tarafı modülün köküne yazıyor ve ikisini birlikte derliyor.

// araclar/araclar.go — derleyiciyi program icinden cagiran ortak yordamlar
package araclar

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

// IkiPaketDerlenirMi kutuphaneKaynagi'ni "sinama/kutuphane" alt paketine, anaKaynagi'ni
// gecici modulun kok dizinine yazip derler; iki paket arasindaki gorunurluk boylece
// gercek bir derleyici cagrisiyla sinanir. Hata metni okunmaz, yalniz basari donuyor.
func IkiPaketDerlenirMi(kutuphaneKaynagi, anaKaynagi 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.MkdirAll(filepath.Join(dizin, "kutuphane"), 0o755)
	os.WriteFile(filepath.Join(dizin, "kutuphane", "kutuphane.go"), []byte(kutuphaneKaynagi), 0o644)
	os.WriteFile(filepath.Join(dizin, "main.go"), []byte(anaKaynagi), 0o644)
	return calistir(dizin)
}

// AyniPaketIkiDosyaDerlenirMi ayni paketin iki dosyasini (biri tanimlayan, biri okuyan
// tarafi) tek bir gecici modulun kok dizinine yazip derler.
func AyniPaketIkiDosyaDerlenirMi(dosyaA, dosyaB 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, "a.go"), []byte(dosyaA), 0o644)
	os.WriteFile(filepath.Join(dizin, "b.go"), []byte(dosyaB), 0o644)
	return calistir(dizin)
}

func calistir(dizin string) bool {
	cmd := exec.Command("go", "build", "./...")
	cmd.Dir = dizin
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	return cmd.Run() == nil
}

IkiPaketDerlenirMi, bir kütüphane kaynağını kutuphane adlı bir alt pakete, onu çağıran kaynağı da modülün köküne yazıyor; ikisi arasındaki tek bağ import satırı. derleyici (kod bloğunda yalnız exec.Command("go", "build", ...) çağrısının argümanı olarak geçiyor) her seferinde tamamen yeni, boş bir dizine karşı çalışıyor — önceki denemenin kalıntısı bir sonraki denemeyi etkilemiyor. Her deneme kendi geçici modülünü kuruyor çünkü aksi hâlde bir denemenin bıraktığı dosya bir sonrakinin sonucunu bulandırabilir; taze bir dizin, ölçülen şeyin yalnız o an yazılan iki kaynak olduğunu garanti ediyor. AyniPaketIkiDosyaDerlenirMi ise farklı bir soru soruyor: iki dosya aynı pakete aitse ne oluyor? Bu iki yordam dışında araclar paketi hiçbir şey bilmiyor — hangi adın “kütüphane”, hangisinin “ana” olduğu, hangi harfin büyük yazıldığı bu paketin dışında, çağıran tarafın kurduğu kaynak metinlerde kalıyor. GOTOOLCHAIN=local ortam değişkeni her iki yordamda da veriliyor; iç derleme çağrısının kendi araç zincirini ağdan indirmeye çalışmaması, yalnız zaten kurulu olanı kullanması için.

Beş Deneme

Aşağıdaki ölçüm aynı işlevi ve aynı yapıyı birden çok biçimde yazıp iki paket arasında sınıyor: bir işlev adı, bir yapı literalinin iki yazılışı ve bir alan okuması.

// main.go — disa aktarimin kurali ve paket siniri dosya siniri degildir
package main

import (
	"fmt"

	"ders/araclar"
)

func goster(soru string, derlendiMi bool) {
	fmt.Printf("%-46s %v\n", soru, derlendiMi)
}

func main() {
	kutuphaneBuyuk := "package kutuphane\n\nfunc Topla(a, b int) int { return a + b }\n"
	anaBuyuk := "package main\n\nimport \"sinama/kutuphane\"\n\nfunc main() { _ = kutuphane.Topla(2, 3) }\n"
	goster("Topla (buyuk harf), baska paketten cagriliyor", araclar.IkiPaketDerlenirMi(kutuphaneBuyuk, anaBuyuk))

	kutuphaneKucuk := "package kutuphane\n\nfunc topla(a, b int) int { return a + b }\n"
	anaKucuk := "package main\n\nimport \"sinama/kutuphane\"\n\nfunc main() { _ = kutuphane.topla(2, 3) }\n"
	goster("topla (kucuk harf), baska paketten cagriliyor", araclar.IkiPaketDerlenirMi(kutuphaneKucuk, anaKucuk))

	kutuphaneYapi := "package kutuphane\n\ntype Kayit struct {\n\tAd    string\n\tgizli int\n}\n"
	anaYapiKur := "package main\n\nimport \"sinama/kutuphane\"\n\nfunc main() { _ = kutuphane.Kayit{Ad: \"x\"} }\n"
	goster("Kayit{Ad: \"x\"}, adli alanla kuruluyor", araclar.IkiPaketDerlenirMi(kutuphaneYapi, anaYapiKur))

	anaYapiPozisyonel := "package main\n\nimport \"sinama/kutuphane\"\n\nfunc main() { _ = kutuphane.Kayit{\"x\", 0} }\n"
	goster("Kayit{\"x\", 0}, pozisyonel olarak kuruluyor", araclar.IkiPaketDerlenirMi(kutuphaneYapi, anaYapiPozisyonel))

	anaYapiOku := "package main\n\nimport \"sinama/kutuphane\"\n\nfunc main() { k := kutuphane.Kayit{Ad: \"x\"}; _ = k.gizli }\n"
	goster("k.gizli, baska paketten okunuyor", araclar.IkiPaketDerlenirMi(kutuphaneYapi, anaYapiOku))

	fmt.Println()
	dosyaA := "package kutuphane\n\nvar deger = 42\n"
	dosyaB := "package kutuphane\n\nfunc Oku() int { return deger }\n"
	goster("deger (kucuk harf), ayni paketin baska dosyasindan okunuyor", araclar.AyniPaketIkiDosyaDerlenirMi(dosyaA, dosyaB))
}
Topla (buyuk harf), baska paketten cagriliyor  true
topla (kucuk harf), baska paketten cagriliyor  false
Kayit{Ad: "x"}, adli alanla kuruluyor          true
Kayit{"x", 0}, pozisyonel olarak kuruluyor     false
k.gizli, baska paketten okunuyor               false

deger (kucuk harf), ayni paketin baska dosyasindan okunuyor true

PK3. Beş çapraz paket denemesinden ikisi derleniyor, üçü derlenmiyor; ayrımı belirleyen şey her seferinde denenen adın ilk harfi ya da yazılışın örtük olarak o harfe dokunup dokunmadığı. PK4. Kayit{Ad: "x"} — adlı alanla kurulan literal — derleniyor: Kayit yapısının dışa aktarılmayan bir gizli alanı olması, yapının kendisinin ya da yalnız dışa aktarılan alanlarının kullanılmasını engellemiyor. PK5. Aynı yapı Kayit{"x", 0} ile, yani alan adı yazılmadan sırayla kurulmaya çalışıldığında derlenmiyor; bu yazılış, ikinci konuma gizli alanına örtük bir değer atıyor ve derleyici bunu paket dışından yapılan bir dışa aktarılmayan alan ataması olarak reddediyor. PK6. k.gizli okuması da derlenmiyor — aynı yapının aynı örneği, farklı bir alan istendiğinde reddediliyor.

Üç ve dördüncü satır (adlı literal, pozisyonel literal) birlikte okunduğunda dışa aktarma kuralının alan başına işlediği ve yazılış biçimine duyarlı olduğu görülüyor: bir yapı bir bütün olarak dışa aktarılsa bile (Kayit büyük harfle başladığı için zaten dışa aktarılıyor burada), her alanı kendi harfine göre ayrı ayrı değerlendiriliyor. Adlı literal yalnız Ad alanını yazdığını söylüyor, gizli alanına dokunmuyor — derleyici bunu kabul ediyor ve gizliye kendi sıfır değerini bırakıyor. Pozisyonel literal ise bütün alanlara sırayla değer atamayı gerektiriyor; ikinci konum gizli alanına düşüyor ve bu, kaynak metinde yazılmasa bile, derleyicinin gözünde dışa aktarılmayan bir alana paket dışından yazmaya eşdeğer. Kaydın bir alanını okumak (k.gizli) da aynı ailede: adı paket dışından anmanın kendisi, okuma ya da örtük yazma fark etmeksizin, reddediliyor. Derleyici üç durumda da hata metnini basmıyor — yalnız derlendi/derlenmedi okunuyor — ama üçünün ortak nedeni aynı: paket dışından görünmeyen bir adı kaynak metinde anmak.

Sınır Paket Sınırıdır, Dosya Sınırı Değildir

PK7. Aynı paketin iki ayrı kaynak dosyası, aralarında hiçbir import olmadan, birbirinin dışa aktarılmayan adını doğrudan paylaşıyor: a.go küçük harfli deger değişkenini tanımlıyor, b.go onu doğrudan okuyor, ve ikisi de derleniyor.

Bu, dersin sınırlayıcı ölçümü. Görünürlük kuralı harfin büyüklüğüne bakıyor demiştik, ama bu kural neye göre uygulanıyor sorusunun yanıtı “dosyaya göre” değil. Yukarıdaki ölçümde a.go ve b.go iki ayrı dosya, iki ayrı derleme biriminde yazılmış gibi görünüyor — ama ikisi de package kutuphane bildirimini taşıyor, ve bu tek satır ikisini derleyicinin gözünde tek bir birleşik ad alanına sokuyor. deger küçük harfle başladığı için paket dışından hiç görünmez, ama aynı paketin herhangi bir dosyasından, hiçbir import yazmadan, doğrudan görünür.

Bu ayrım sık gözden kaçıyor çünkü birçok dilde derleme birimi dosyanın kendisidir ve bir dosyanın gördüğü ad, o dosyanın kendi bildirimleriyle sınırlıdır. Go’da derleme birimi pakettir; bir paketin kaç dosyaya bölündüğü yalnız düzenleme kolaylığı için verilmiş bir karardır, görünürlüğü hiç değiştirmez. Bir paketi on dosyaya bölmek ile tek dosyada tutmak, o paketin dışa aktardığı ve dışa aktarmadığı adlar açısından birebir aynı sonucu verir — fark yalnız kaynağı okuyan insanın hangi dosyaya bakacağıdır, derleyicinin gördüğü ad kümesi değil.

Bunun pratik bir sonucu var: bir paketi birden çok dosyaya bölmek yalnız okunabilirlik içindir, hiçbir erişim kaybına yol açmaz. Gerçek paketler genellikle bu yüzden konuya göre bölünür — bir dosya tipleri taşır, bir başkası o tiplerin üzerindeki işlevleri, bir üçüncüsü hata değerlerini — ve bu dosyalar birbirinin dışa aktarılmayan adlarını hiçbir ekstra bildirim yazmadan paylaşır. Ölçüm bunu tam tersinden de doğruluyor: a.go ile b.go arasında degeri taşıyan hiçbir köprü kod yok, çünkü köprüye gerek yok — ikisi package kutuphane satırını paylaştığı an aynı ad alanına giriyorlar. Eğer görünürlüğün sınırı dosya olsaydı, bir paketi birden çok dosyaya bölmenin bedeli her seferinde yeni bir “iç API” kurmak olurdu; Go’da bu bedel hiç yok, çünkü sınır zaten dosyanın değil paketin sınırı.

Bu, kapsam (M01/K02 Programlama Temelleri kursunda kurulan kavram) ile aynı şey değildir. Kapsam bir adın nerede görülebileceğini bir blok, işlev ya da paket düzeyinde belirler; dışa aktarma kuralı ise paket düzeyinde zaten görünür olan bir adın, o paketin dışından görülüp görülemeyeceğine karar verir. deger paketin her dosyasında aynı paket kapsamına aittir; dışa aktarma kuralı bu kapsamın kendisini değiştirmiyor, yalnız kapsamın paket sınırını aşıp aşamayacağını belirliyor.

Özet

  • Go’da görünürlük için ayrı bir bildirim sözdizimi yoktur; bir adın dışa aktarılıp aktarılmadığı yalnız ilk harfinin büyük ya da küçük olmasıyla belirlenir.
  • Bu kural işlev, tip, değişken, sabit ve yapı alanı için aynı biçimde işler; bir yapı bir bütün olarak dışa aktarılsa bile her alanı kendi harfine göre ayrı değerlendirilir.
  • Dört çapraz paket denemesinden ikisi (büyük harfli işlev, yalnız dışa aktarılan alanı kuran yapı literali) derlenir; ikisi (küçük harfli işlev, dışa aktarılmayan alanı okuma) derlenmez.
  • Sınırlayıcı ölçüm: derleme birimi dosya değil pakettir; aynı paketin iki ayrı dosyası hiçbir import olmadan birbirinin küçük harfli adını paylaşır.
  • Derleyici bu derste de program içinden çağrıldı ve yalnız derlendi/derlenmedi sonucu okundu; hata metni hiçbir ölçümde basılmadı.

Sonraki Adım

Bu derste sınırın nerede durduğu — pakette, dosyada değil — koşturularak ölçüldü, ve görünürlüğün tamamen derleme anında, tek bir harfe bakılarak çözüldüğü altı ayrı denemeyle doğrulandı. Ama bir paket kendi başına yeterli değildir; çoğu zaman başka paketlere bağımlıdır, ve modülün gördüğü bu paketler her zaman aynı modülün içinde durmaz — ayrı bir modülden gelirler ve bir sürüm taşırlar. O sürümün kaynakta hangi satırda, hangi biçimde yazılacağı çoğu zaman hiç belirtilmez; bazen doğrudan yazılır, bazen bir bağımlılığın bağımlılığından türetilir. Sıradaki ders bu soruyu ele alıyor: bir bağımlılığın sürümü yazılmadığında modül dosyası onun yerine neyi seçiyor, bu seçim hangi kurala göre yapılıyor, ve seçilen sürüm neden her zaman mevcut en yeni sürüm olmuyor?

İ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