Ders 01 / 18
Go'nun Tasarım Amaçları
Go derleyicisinin reddettiği dört şey — kullanılmayan değişken, kullanılmayan içe aktarım, yazılmayan dönüş değeri, örtük tip dönüşümü — program içinden çağrılan bir derleyiciyle koşturulur; dördü de derlemeyi durdurur ve örtük dönüşümün reddi aynı işi yapan kaynağı 15 karakterden 22 karaktere çıkarır.
İçindekiler
M08’in altı kursu, aynı platforma altı ayrı soru sordu; sonuncusu bir davranışın kaynakta hiçbir çağrının doğurmayabileceğini ölçtü — sınıf yolunda bulunan bir sağlayıcı, kaynakta yazılı hiçbir satır olmadan devreye girebiliyordu. Bu kurs aynı kapıdan girer ama soruyu platformdan dilin kendisine taşır: bir dil, yazmadığın şeyin karşılığını kendi belirler. Kimi yerde bu karşılık tanımlı bir değerdir, kimi yerde dil yazılmamış olmayı hiç kabul etmez ve kaynağı derlemeyi reddeder.
Bu ilk ders reddi ölçer. Go derleyicisi, çalışma zamanında hiçbir soruna yol açmayacak dört biçimi bile kabul etmez: kullanılmayan bir değişken, kullanılmayan bir içe aktarım, yazılmayan bir dönüş değeri, örtük bir tip dönüşümü. Dördü de başka dillerde çoğunlukla bir uyarıdır; burada kaynak hiç sınıf dosyasına ya da yürütülebilir dosyaya dönüşmez. Sorulan soru şu: bunlardan biri yazılmadığında derleyici ne yapıyor, ve bu ret gerçekten bir kısıtlama mı, yoksa yazılması gereken şeyi başka bir yere mi taşıyor?
Kursun geri kalanı boyunca aynı üçlü soru sorulacak: yazılmayan bir şey dilde tanımlı bir karşılık mı buluyor, derleyici tarafından reddediliyor mu, yoksa sonuç çalışma zamanına kalıp açıkta mı kalıyor? Bu ilk ders, üç yanıttan yalnız ortadakini — reddi — ele alıyor, çünkü ret, dilin sınırlarını en keskin çizen yanıttır: dil bir şeye izin vermediğinde, o şeyin nasıl davranacağını konuşmaya bile gerek kalmaz.
Zincir ve Bu Dersin Payı
Programlama Temelleri kursu (M01/K02) değişken tanımını, işleçleri, açık ve örtük dönüşümü Python’la kurdu; orada örtük dönüşüm çoğunlukla sorunsuz geçen bir çalışma zamanı davranışıydı. Bu kavram burada tekrar kurulmaz — farkı tek cümleyle yazmak yeter: Go’da aynı örtüklük bir çalışma zamanı olayı değil, bir derleme zamanı reddidir. Bilgisayarlar Nasıl Çalışır kursu (M01/K01) derleyici–yorumlayıcı ayrımını kavram olarak kurdu; bu derste derleyici bir kavram değil, program içinden çağrılan bir araçtır ve verdiği tek yanıt geçip geçmediğidir.
Veri Yapıları kursu (M01/K03) diziyi, listeyi ve sözlüğü soyut veri tipi olarak kurdu ve karmaşıklıkla karşılaştırdı; bu müfredat o karşılaştırmayı tekrarlamaz. M09’un payı, kurulmuş kavramların Go’da hangi düzenekle gerçekleştiğidir — bu ilk derste düzenek henüz bir veri tipi değil, derleyicinin kaynağa karşı aldığı bir tavırdır. Sonraki dersler bu düzeneği sırasıyla değişkenlere, tiplere ve denetim akışına taşıyacak.
Üç Tasarım Amacı, Tek Ölçülebilir Sonuç
Go’nun tasarım amaçları arasında üçü sık anılır: basitlik, derleme hızı, okunabilirlik. Bu ders üçünü de doğrudan koşturmaz — süre bir ölçüm birimi değildir, çünkü bir derlemenin ne kadar sürdüğü makineye ve o anki yüke bağlıdır ve bir koşumdan öbürüne değişir. Ama üçünün de kaynakta görünür bir iz bıraktığı bir yer vardır: derleyicinin neyi reddettiği. Bir dil her yapıyı kabul ederse, okuyanın hangi yolun kastedildiğini kaynaktan çıkarması gerekir; bir dil bir yapıyı reddederse, o yapıyı yazan taraf niyetini kaynağa taşımak zorunda kalır. Basitlik burada “az karakter yazmak” değil, dilin kabul ettiği biçim sayısının az olmasıdır: bir işi yapmanın kaç yolu varsa, derleyicinin reddetmesi gereken yol sayısı da o kadar artar. Derleme hızı, bu ret kararlarının dosya sırasına bağlı olmamasından gelir — bir dosyadaki kullanılmayan içe aktarım, başka bir dosyanın nasıl derlendiğini beklemeden, o dosya tek başına okunurken görülebilir. Okunabilirlik ise doğrudan bu dersin ölçtüğü şeydir: dört ret de, kaynağı okuyan tarafın kaynaktaki her adı ve her tipi tam olarak yazıldığı gibi okuyabilmesi için var.
Bu üç amaç birbiriyle çelişebilir görünür — az yazmak ile niyeti açıkça yazmak aynı yönde durmaz. Bu dersin geri kalanı bu gerilimi ölçer: reddedilen dört biçim gerçekten daha kısa bir yazımı mı engelliyor, yoksa örtük bırakılabilecek bir şeyi mi görünür kılıyor?
Derleyici Program İçinden Çağrılır
Ölçümün tamamı tek bir soruya iner: bir kaynak metni derlenir mi? Bu soruyu dış bir araca sormadan, standart kitaplığın süreç başlatma ve dosya yazma yeteneğiyle yanıtlarız. Kaynak metni geçici bir dizine bir modül dosyasıyla birlikte yazılır, derleyici o dizinde çalıştırılır ve yalnız çıkış durumu okunur.
// derleyici.go — derleyiciyi program icinden cagiran yardimci
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
}
func siniflaKaynak(kaynak string) string {
if derlenirMi(kaynak) {
return "derlendi"
}
return "derlenmedi"
}
DT1. Derleyici burada bir kavram değil bir çağrıdır: derlenirMi, kaynağı geçici bir
pakete yazıp derleyiciyi bir alt süreç olarak koşturur ve yalnız çıkış durumunu okur. Hata
metni hiç okunmaz, çünkü hata metninin biçimi sürüme bağlı veridir; kursun tamamında ölçülen
şey “neden reddedildi” değil, “reddedildi mi”dir.
Bu ayrımın bir bedeli var: derlenirMi yalnız true ya da false döndürdüğü için, bir kaynak
beklenenden başka bir nedenle de reddedilmiş olabilir ve ölçüm bunu ayırt edemez. DT2 bu
riski kapatıyor — dört kaynağın her biri elle kurulmuş ve tek bir yazılmamışlık dışında
derlenebilir olduğu doğrulanmıştır. Bu, derste gösterilen dört satırlık tabloyu okurken
akılda tutulması gereken bir sınırdır: derlendi/derlenmedi bir hata ayıklama aracı değil,
bir sınıflandırma aracıdır.
DT2. Aşağıdaki dört kaynağın her biri, tek bir yazılmamışlık dışında derlenebilir durumdadır. Ret, o tek nedenden gelir — başka bir sözdizim hatasından değil. Bu derste ve kursun geri kalanında ret ölçülürken bu koşul hep sağlanır.
Bu yöntemin bir dış araca ihtiyaç duymaması iki şeyi birden sağlıyor. Birincisi, ölçüm
belirlenimcidir: aynı kaynak metni her koşumda aynı yanıtı verir, çünkü derleyicinin kendisi
çağrılıyor, davranışı taklit eden ayrı bir yorumlayıcı değil. İkincisi, ölçüm taşınabilirdir:
derlenirMi yalnız standart kitaplığın süreç başlatma ve geçici dosya yazma yeteneğini
kullanıyor, kursun geri kalanında da aynı desen tekrarlanacak.
Dört Ret, Dört Neden
Aşağıdaki dört kaynak, aynı çerçevede yan yana koşturulur. Her biri, çalışma zamanında hiçbir soruna yol açmayacak bir eksikliği taşıyor — hiçbiri sonsuz döngü, bellek sınırı aşımı ya da tip uyuşmazlığı değildir. Buna rağmen dördü de derlenmez.
// main.go — dort ret nedeni yan yana
package main
import "fmt"
func main() {
fmt.Printf("%-32s %s\n", "neden", "sonuc")
fmt.Printf("%-32s %s\n", "kullanilmayan degisken", siniflaKaynak(`package sinama
func F() { x := 1 }`))
fmt.Printf("%-32s %s\n", "kullanilmayan ice aktarim", siniflaKaynak(`package sinama
import "fmt"
func F() {}`))
fmt.Printf("%-32s %s\n", "donus degerinin yazilmamasi", siniflaKaynak(`package sinama
func F() int { }`))
fmt.Printf("%-32s %s\n", "ortuk tip donusumu", siniflaKaynak(`package sinama
func F() { var a int32 = 1; var b int64 = a; _ = b }`))
}
neden sonuc kullanilmayan degisken derlenmedi kullanilmayan ice aktarim derlenmedi donus degerinin yazilmamasi derlenmedi ortuk tip donusumu derlenmedi
Dört satır da aynı sonucu veriyor, ama aynı gerekçeden gelmiyor. Kullanılmayan değişken
ile kullanılmayan içe aktarım, kaynakta yazılan bir şeyin hiç okunmamasını reddeder — dil,
“belki sonra kullanırım” diye bir ara durumu tanımaz; bir ad ya kullanılır ya da kaynakta hiç
yazılmaz. Dönüş değerinin yazılmaması farklı bir sınıftandır: imzada int sözü verilmiş
ama gövde hiçbir değer üretmiyor, derleyici sözü tutmayan gövdeyi kabul etmiyor. Örtük tip
dönüşümü ise üçüncü bir sınıf: int32 bir int64 değişkenine doğrudan atanamıyor, hedefteki
bit genişliği kaynaktakinden farklı olduğu için derleyici genişlemenin yazılı olmasını
istiyor.
Üçünün ortak noktası, hiçbirinin çalışma zamanında gözlemlenebilir bir hataya yol açmayacak
olmasıdır. Kullanılmayan bir değişken programı çökertmez, kullanılmayan bir içe aktarım hiçbir
şeyi bozmaz, int32den int64e genişleme hiçbir veri kaybetmez. Derleyici bunları çalışma
zamanı riskine göre değil, kaynağın okunurluğuna göre reddediyor: kullanılmayan bir ad
okuyanı yanıltır, örtük bir genişleme okuyana hangi tipin nerede durduğunu gizler. Ret burada
bir güvenlik önlemi değil, yazılmayanın karşılığını daraltma kararıdır — dil bu dört
durumda “tanımlı bir şey koymak” yerine “hiç izin vermemek”i seçiyor.
İki rettin (kullanılmayan değişken, kullanılmayan içe aktarım) ortak bir kaçış yolu var: dilin
boş tanımlayıcısı, _. Bir değeri _e atamak, “bu değeri biliyorum ama kullanmayacağım”
demenin yazılı bir yoludur; derleyici bunu kullanım sayar ve reddetmez. Bu, ilk bakışta ret
kararını delen bir kaçamak gibi görünür, ama aslında tam tersini yapar: _ yazmak, kullanılmayan
bir adı sessizce bırakmak ile aradaki farkı ortadan kaldırır, çünkü ikisi de kaynakta aynı
ölçüde görünür olur — biri gerçek bir ad, öbürü kasıtlı bir “önemsemiyorum” işareti. Bu tanımlayıcı
kursun ilerleyen derslerinde tekrar karşımıza çıkacak: çoklu dönüş değerlerinden birini atlarken
ve bir sözlükte anahtarın varlığını sorarken de aynı sözcük kullanılıyor.
Her ret, bir düzeltme sırasında ortaya çıkan somut bir durumu yakalıyor. Kullanılmayan içe aktarım, bir kod parçası taşınıp bir çağrı silindiğinde geride kalan bir satırdır; derleyici onu tutmaya izin verseydi, kaynak zamanla artık kullanılmayan bağımlılıklarla dolardı ve hangi içe aktarımın gerçekten gerekli olduğunu görmek güçleşirdi. Kullanılmayan değişken de benzer bir iz taşır: bir hesaplamanın ara sonucu bir adla tutulup sonra kullanılmadan bırakıldığında, bu çoğunlukla yarım kalmış bir düzenlemenin işaretidir. Yazılmayan dönüş değeri farklı bir kaynaktan geliyor — imza bir söz, gövde o sözü tutmalı; derleyici bunu, çağıran tarafın imzaya bakarak kurduğu varsayımı korumak için zorluyor. Örtük tip dönüşümü ise en dolaylı olanı: iki tam sayı tipi aynı ailede olsa bile bit genişlikleri farklıysa, bir değeri diğerine sessizce kopyalamak, kaynağı okuyan tarafın gözünden hiç olmamış bir genişleme demektir; derleyici bu genişlemeyi görünür kılmayı, sessizce geçirmeye tercih ediyor.
Sınırlayıcı Ölçüm: Reddetmek Daha Az Yazmak Demek Değildir
Ret kararının bedelsiz olduğunu düşünmek yanıltıcıdır. Örtük dönüşümün reddini ele alalım:
var b int64 = a satırı, a bir int32 iken derlenmiyor. Aynı işi yapan, derlenen satır
var b int64 = int64(a)dır — dönüşüm çağrısı yazılmak zorunda. İki satırı birebir
karşılaştıralım.
// sinirlayici.go — reddetmek daha az yazmak demek degil mi
package main
import "fmt"
func main() {
ortukSatir := "var b int64 = a"
acikSatir := "var b int64 = int64(a)"
ortukKaynak := "package sinama\nfunc F() { var a int32 = 1; " + ortukSatir + "; _ = b }"
acikKaynak := "package sinama\nfunc F() { var a int32 = 1; " + acikSatir + "; _ = b }"
fmt.Printf("%-38s %-11s uzunluk=%d\n", "ortuk donusum", siniflaKaynak(ortukKaynak), len(ortukSatir))
fmt.Printf("%-38s %-11s uzunluk=%d\n", "acik donusum", siniflaKaynak(acikKaynak), len(acikSatir))
}
ortuk donusum derlenmedi uzunluk=15 acik donusum derlendi uzunluk=22
Sonuç ilk bakışta tersine dönmüş görünüyor: derlenmeyen satır kısa (15 karakter), derlenen
satır uzun (22 karakter). Reddetmek, kaynağı kısaltmıyor — tam tersini yapıyor. Derleyici,
anın genişletileceğini varsaymak yerine bu genişletmenin kaynakta görünür olmasını
istiyor ve bunun bedelini yazan tarafa yüklüyor: yedi karakterlik bir çağrı.
DT3. Burada ölçülen birim karakterdir, çalışma süresi ya da bellek değildir; iddia “reddetmek yavaşlatır” değil, “reddetmek kısaltmaz”dır. On beş karakterlik fark küçük görünse de, aynı kalıp bir kaynağın her genişleme noktasında tekrarlanır — okunabilirlik kazancı, satır başına sabit bir yazım bedeliyle satın alınıyor.
Dördü aynı yönde değil. Kullanılmayan içe aktarımın düzeltmesi bir silmedir — satır kaynaktan tamamen çıkar ve kaynak kısalır. Örtük tip dönüşümünün düzeltmesi ise bir eklemedir — var olan bir satıra bir çağrı sarılır ve kaynak uzar. İki ret de aynı sınıftan (derlenmedi) sonuç verir, ama düzeltmenin yönü tersinedir. Bu, “reddetmek” tek bir yazım maliyeti taşıdığı izlenimini kırıyor: bazı ret nedenleri kaynağı sadeleştirmeye, bazıları niyeti açıkça yazmaya zorluyor; ortak olan tek şey, ikisinin de kaynakta belirsiz bir aralık bırakmamasıdır.
Bu, dördüncü rette (kullanılmayan değişken) de aynı biçimde görülür: x := 1 satırını silmek
kaynağı kısaltır, ama xi bir yere kullanmak (_ = x yazmak) da kısaltmaz, yalnızca bir
sözcük ekler. Reddin karşılığı hiçbir zaman “daha az yaz” değildir; ya sil ya da niyetini
açıkça yaz. Go’nun tasarım amaçları arasında sayılan basitlik, kod uzunluğunu değil,
kaynakta neyin göründüğünü hedefliyor: bir okuyucu, int64(a) satırını gördüğünde genişlemenin
kasıtlı olduğunu bilir; var b int64 = a satırını görseydi bunu bilemezdi.
Bu okuma, önceki bölümde açılan gerilimi kapatıyor. Basitlik ile niyeti açıkça yazmak, kaynağın uzunluğu düzeyinde çelişir ama kaynağın kural sayısı düzeyinde çelişmez: derleyicinin tanıdığı dönüşüm kuralı tektir — genişleme her zaman yazılır — ve bu tek kural, “hangi durumda örtük dönüşüm güvenlidir” sorusunu okuyandan tamamen kaldırıyor. Okuyan taraf, bir atamanın örtük mü açık mı olduğunu ayrıca sorgulamak zorunda kalmıyor, çünkü örtük olanı derleyici zaten geçirmiyor. Basitlik burada azalan karakter sayısında değil, azalan olası yorum sayısında duruyor.
Derleme hızıyla bağ da aynı noktadan kuruluyor. Dört ret de, kararını verirken kaynağın geri kalanını beklemez: kullanılmayan bir içe aktarım, o dosya tek başına okunurken bellidir; örtük bir dönüşüm, atamanın iki yanındaki tipe bakılarak yerinde görülür. Hiçbiri, programın başka bir yerinde ne olduğuna bağlı bir çıkarım gerektirmiyor. Bu derste süre hiç ölçülmedi, ama ret kararlarının yerelliği — her birinin yalnız kendi bulunduğu ifadeye bakması — derleme hızının nereden geldiğini gösteren gözlemlenebilir bir iz bırakıyor.
Özet
- Derleyici bu derste ve kursun geri kalanında program içinden çağrılır; yalnız geçip geçmediği okunur, hata metni hiç okunmaz.
- Kullanılmayan değişken, kullanılmayan içe aktarım, yazılmayan dönüş değeri ve örtük tip dönüşümü — dördü de çalışma zamanında zararsız olduğu hâlde derlemeyi durduruyor.
- Üç neden üç ayrı sınıftandır: yazılıp okunmayan bir ad, sözü tutmayan bir gövde, kaynakta görünmeyen bir genişleme.
- Örtük dönüşümün reddi kaynağı kısaltmaz: derlenmeyen satır 15 karakter, derlenen karşılığı 22 karakterdir.
- Ret, yazılmayanın karşılığını daraltma kararıdır — dil bu dört durumda tanımlı bir değer koymak yerine hiç izin vermemeyi seçer.
Sonraki Adım
Dört ret nedeni, bir kaynağın fazladan yazdığı ya da eksik bıraktığı şeylerle ilgiliydi; hiçbiri kaynağın çalışabilmesi için gereken en küçük kümeyle ilgili değildi. Sıradaki ders bu küçük kümeyi sorar: bir kaynağın derlenip çalışabilmesi için hangi parçalar zorunludur, ve bu parçalardan biri kaynağın kendisinde değil de modül dosyasında yazılıyorsa, onu kaldırmak neye mal olur?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.