İçeriğe geç
academia.sh

Ders 03 / 18

Değişkenler ve Sabitler

Kısa bildirim bir değişkenin tipini kaynakta hiç yazmadan belirliyor, altı sabitlik bir iota dizisinin yalnız ikisinde değer kaynakta yazılı — dördü bir önceki ifadenin tekrarından geliyor — ve aynı kısa bildirim satırı paket düzeyinde reddediliyor, fonksiyon içinde kabul ediliyor.

İçindekiler

Önceki ders bir kaynağın çalışabilmesi için hangi parçaların zorunlu olduğunu ölçtü, ama func main() {} boyunca gövde hep boştu — hiçbir değere ne yazılıp ne yazılmadığı hiç sorulmadı. Bu ders o boşluğa giriyor. Bir değişken tanımlarken tipini yazmak zorunda mısın, kısa bildirim yazmadığın tipi nereden çıkarıyor, ve sıralı bir sabit dizisinde aradaki değerleri kim dolduruyor?

Programlama Temelleri kursu (M01/K02) değişken tanımını ve bağlamayı Python’la kurdu; orada bir adın bir değere bağlanması tek bir yazımla oluyordu ve tip kaynakta hiç yer almıyordu. Bu kavram burada tekrar kurulmaz — farkı tek cümleyle yazmak yeter: Go’da bir adın bir tipi vardır ve bu tip ya yazılır ya da bir değerden çıkarılır; hiçbir değişken tipsiz kalmaz. Bu dersin sorduğu soru, o çıkarımın nereden geldiğidir.

Kısa Bildirim Tipi Nereden Çıkarıyor

Go’da bir değişken iki biçimde tanımlanır: var ad tip = değer ya da kısa bildirim, ad := değer. İkincisinde tip hiç yazılmaz; derleyici, atanan değerin kendi tipine bakarak değişkenin tipini belirler. Bu, “yazılmayan bir şeyin tanımlı bir karşılığı var” örneklerinden biridir — tip yazılmadı, ama değişkenin bir tipi olmaması diye bir seçenek yok, derleyici o tipi değerden çıkarıyor.

Çıkarımın hangi tipe vardığını, çalışma zamanında %T biçim doğrulayıcısıyla sorabiliriz; bu doğrulayıcı bir değerin statik tipinin adını yazdırır ve bu ad derleme zamanında sabitlenir — her koşumda aynı yanıtı verir.

// tur.go — kisa bildirimin cikardigi tip
package main

import "fmt"

func main() {
	tamsayi := 42
	kayanNokta := 3.14
	dizgi := "metin"
	mantiksal := true

	fmt.Printf("%-12s %-8s deger=%v\n", "tamsayi", fmt.Sprintf("%T", tamsayi), tamsayi)
	fmt.Printf("%-12s %-8s deger=%v\n", "kayanNokta", fmt.Sprintf("%T", kayanNokta), kayanNokta)
	fmt.Printf("%-12s %-8s deger=%v\n", "dizgi", fmt.Sprintf("%T", dizgi), dizgi)
	fmt.Printf("%-12s %-8s deger=%v\n", "mantiksal", fmt.Sprintf("%T", mantiksal), mantiksal)

	var genisTip float64 = 42
	fmt.Printf("%-12s %-8s deger=%v\n", "genisTip", fmt.Sprintf("%T", genisTip), genisTip)
}
tamsayi      int      deger=42
kayanNokta   float64  deger=3.14
dizgi        string   deger=metin
mantiksal    bool     deger=true
genisTip     float64  deger=42

DT7. %T, kaynağın kendisinden okunan statik bir addır; makineye, sürüme ya da koşuma bağlı hiçbir bilgi taşımaz. Bu derste ve kursun geri kalanında tip adı okumak için hep bu doğrulayıcı kullanılacak, çünkü ortama bağlı veri yasağını hiç ihlal etmiyor.

Bu, ölçüm için önemli bir ayrımı gösteriyor: %Tnin yazdırdığı ad, bir bellek adresi ya da bir değişkenin kendisi değil, derleyicinin o değişkene atadığı tip bilgisinin adıdır. İki farklı koşumda aynı kaynak çalıştırıldığında %T her zaman aynı dizgiyi üretir, tıpkı bir önceki derste derlenirMinin her zaman aynı derlendi/derlenmedi yanıtını üretmesi gibi. Kursun ölçüm araçlarının hepsi bu iki özelliği paylaşıyor: belirlenimci olmak ve ortama bağlı hiçbir şey okumamak.

İlk dört satır, kısa bildirimin dört ayrı değerden dört ayrı tipe vardığını gösteriyor: tam sayı yazınca int, ondalıklı yazınca float64, tırnak içinde yazınca string, true/false yazınca bool. Hiçbirinde tip adı kaynakta geçmiyor; kısa bildirimin solundaki := işareti, “bu adın tipini sağdaki değerden çıkar” demenin kısaltmasıdır.

Bu çıkarım, atanan değerin yazım biçimine bakarak çalışıyor, çalışma zamanındaki bir sınamaya değil. 42 yazıldığında derleyici bunun bir tam sayı sabiti olduğunu kaynağı okurken görüyor; hiçbir değer üretilmeden, program hiç çalıştırılmadan bu karar veriliyor. Bu, kısa bildirimin de bir derleme zamanı kararı olduğunu gösteriyor — yalnız tipin adı kaynakta yazılmıyor, kararın kendisi hâlâ derleme sırasında, çalışma zamanından önce alınıyor. Kısa bildirimin çalışma zamanına bıraktığı hiçbir şey yok; yazılmayan yalnızca görünür harf dizisidir, karar değildir.

var ile Yazılan Tip Nasıl Ayrılıyor

Beşinci satır farklı bir yol izliyor. 42 sayısı kısa bildirimle yazılsaydı int olurdu — ilk satır bunu zaten gösterdi. Burada aynı 42 değeri, var genisTip float64 = 42 satırıyla açıkça float64 yapılıyor. Kısa bildirim tipi değerden çıkarırken, var tipi elle belirlemenin yolunu açıyor; ikisi arasındaki fark, kimin karar verdiğidir. Kısa bildirimde karar değere aittir, varda karar yazan tarafa aittir.

Bu, önceki derste ölçülen örtük dönüşüm reddiyle karıştırılmamalı. var genisTip float64 = 42 bir dönüşüm değil, bir ilk değer atamasıdır42 burada tipsiz bir sabittir ve tipsiz sabitler hedef tipe göre biçimlenir. Bir dönüşüm, zaten tipi belli olan bir değişkeni başka bir değişkene atamaya çalıştığında devreye giriyordu (var a int32 = 1; var b int64 = a). Aradaki fark ilerleyen derste tekrar ölçülecek; burada not edilmesi gereken, kısa bildirimin sonucunu var ile bastırmanın bir ret değil, bir tercih olduğudur.

var yazımının bir üstünlüğü daha var: bir ilk değer olmadan da kullanılabiliyor. var sayac int yazmak, sayacı hiçbir değer vermeden tanımlıyor ve derleyici bu satırı hiç reddetmiyor — kısa bildirimin tersine, var bir değer istemez. Bu durumda değişkenin aldığı değer, bu kursun ilerleyen bir dersinin konusu olan sıfır değerdir; burada yalnız şu not edilsin: varın tip yazmaya izin vermesi ile değer yazmadan da çalışabilmesi aynı kökten geliyor — ikisi de, kısa bildirimin aksine, “bir değerden çıkarım” zorunluluğuna bağlı değil.

Iota: Yazılmayan Sabit Değerleri Kim Dolduruyor

Sabitler, const ile tanımlanır ve bir grup içinde art arda sıralandığında iota adlı özel bir sayaç kullanılabilir. iota, bir const bloğundaki her satırda sıfırdan başlayıp bir artar; bir satırın kendi ifadesi yoksa, satır bir önceki satırın ifadesini olduğu gibi tekrar eder ve yalnız iotanın değeri güncellenir.

Bunun çözdüğü bir sorun var: bir durum kümesini elle numaralandırmak (Beklemede = 0, Calisiyor = 1, Tamamlandi = 2, …) hem tekrar eden bir yazımdır hem de araya yeni bir durum eklendiğinde bütün sayıların elle kaydırılmasını gerektirir. iota, bu numaralandırmayı satırın sırasına bağlıyor; bir satır araya eklendiğinde ondan sonraki bütün değerler otomatik kayar, hiçbiri elle güncellenmez. Bu, sayıların kendisinin önemli olmadığı, yalnız sırasının ve tekilliğinin önemli olduğu durum kümeleri için kurulmuş bir düzenek.

// sabitler.go — iota ile alti sabit, kacinin degeri kaynakta yazili
package main

import "fmt"

const (
	Beklemede  = iota
	Calisiyor
	Tamamlandi
	Basarisiz  = iota * 100
	Iptal
	ZamanAsimi
)

func main() {
	fmt.Printf("%-12s %d\n", "Beklemede", Beklemede)
	fmt.Printf("%-12s %d\n", "Calisiyor", Calisiyor)
	fmt.Printf("%-12s %d\n", "Tamamlandi", Tamamlandi)
	fmt.Printf("%-12s %d\n", "Basarisiz", Basarisiz)
	fmt.Printf("%-12s %d\n", "Iptal", Iptal)
	fmt.Printf("%-12s %d\n", "ZamanAsimi", ZamanAsimi)
}
Beklemede    0
Calisiyor    1
Tamamlandi   2
Basarisiz    300
Iptal        400
ZamanAsimi   500

DT8. Altı sabitin yalnız ikisinde (Beklemede, Basarisiz) bir ifade kaynakta yazılı; dördünde (Calisiyor, Tamamlandi, Iptal, ZamanAsimi) satırda yalnız bir ad duruyor, değer hiç yazılmıyor. Bu dördünün değeri iki kaynaktan geliyor: satırın kendi sırasındaki iota değeri ve bir önceki satırdan tekrarlanan ifade.

Tablo bunu doğruluyor. Calisiyor ve Tamamlandi, Beklemedenin ifadesini (iota) tekrar ediyor ama kendi satırlarındaki iota değeriyle (sırasıyla 1 ve 2) hesaplanıyor — bu yüzden 0, 1, 2 diye artıyorlar. Iptal ve ZamanAsimi de aynı mantıkla Basarisizin ifadesini (iota * 100) tekrar ediyor, ama kendi iota değerleriyle (4 ve 5) çarpılıyor — 300, 400, 500 diye artıyorlar. iota bloğun tamamında tek bir sayaçtır; grup içinde ikinci bir ifade göründüğünde sıfırlanmıyor, satır sırasına göre kesintisiz artmaya devam ediyor.

Bu davranış, “yazılmayanın karşılığı” sorusuna kursun şimdiye kadarki en dolaylı yanıtını veriyor. Bir satırda hiçbir şey yazılmaması, o satırın rastgele ya da boş bir değer alacağı anlamına gelmiyor; iki ayrı kural — sıradaki iota değeri ve bir önceki ifadenin tekrarı — birleşip tam olarak tanımlı bir sonuç üretiyor. Yazılmayan dört değerin dördü de bu yüzden tanımlı sınıfına giriyor, açıkta değil: sonuç koşumdan koşuma değişmiyor, kaynağı okuyan biri kâğıt üzerinde de aynı sayılara ulaşabilir.

Bu örnekte her sabit fmt.Printf çağrısında kullanıldığı için kullanılmayan değişken reddiyle karşılaşmıyoruz; ilk derste ölçülen kural burada da geçerli — bir sabit paket düzeyinde tanımlandığında kullanılmama reddi zaten uygulanmıyor, çünkü Go bu reddi yalnız yerel değişkenlere uyguluyor. Paket düzeyinde tanımlanan bir sabit ya da değişken, hiç okunmasa bile derlemeyi durdurmaz. Bu ayrım bir sonraki bölümde tekrar karşımıza çıkacak.

Bu tek kural, ilk dersteki reddi hiç çelişmeden daraltıyor. İlk derste ölçülen “kullanılmayan değişken” reddi, dört ret arasında en sık karşılaşılanıydı ve orada hangi bağlamda geçerli olduğu hiç sorulmamıştı. Burada görülüyor ki o ret evrensel değil: yerel bir değişkeni kullanmadan bırakmak reddedilir, paket düzeyinde bir sabiti kullanmadan bırakmak reddedilmez. Bunun bir nedeni var — paket düzeyindeki bir sabit, aynı paketin başka bir dosyasında, hatta başka bir paketten dışa aktarıldığında tamamen başka bir dosyada kullanılabilir; derleyici, tek bir dosyayı okurken bu kullanımın ilerde başka bir yerde olup olmayacağını bilemez. Yerel bir değişken ise fonksiyonun kendi gövdesiyle sınırlıdır ve derleyici onun kullanılıp kullanılmadığını aynı bakışta görebilir.

iotanın bir de göz ardı etme yolu var: bir satırda ad yerine boş tanımlayıcı _ yazılırsa, iota yine artar ama o sıraya karşılık gelen hiçbir ad üretilmez. Bu, ilk derste ölçülen boş tanımlayıcının bir başka kullanımı — orada kullanılmayan bir değeri isimlendirmeden geçmenin yoluydu, burada sıradaki bir numarayı atlamanın yolu oluyor. İkisinde de aynı sözcük, “bunu biliyorum ama bir ada bağlamayacağım” anlamına geliyor.

Sınırlayıcı Ölçüm: Kısa Bildirim Her Yerde Çalışmaz

Kısa bildirim, göründüğü kadar evrensel değil. Bunu ölçmek için ilk dersteki derleyici çağrısına dönüyoruz: aynı satırı iki farklı bağlamda deneyip sonucu sınıflayacağız.

// 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 sinif(ok bool) string {
	if ok {
		return "derlendi"
	}
	return "derlenmedi"
}

Aynı x := 5 satırını iki farklı bağlamda deneyelim: bir kez bir fonksiyonun dışında, paket düzeyinde; bir kez bir fonksiyonun içinde.

// sinirlayici.go — kisa bildirim paket duzeyinde reddedilir mi
package main

import "fmt"

func main() {
	paketDuzeyi := `package sinama
x := 5
func F() { _ = x }`
	fonksiyonDuzeyi := `package sinama
func F() { x := 5; _ = x }`

	fmt.Printf("%-24s %s\n", "paket duzeyinde", sinif(derlenirMi(paketDuzeyi)))
	fmt.Printf("%-24s %s\n", "fonksiyon icinde", sinif(derlenirMi(fonksiyonDuzeyi)))
}
paket duzeyinde          derlenmedi
fonksiyon icinde         derlendi

DT9. İki kaynak da aynı x := 5 satırını taşıyor; tek fark, satırın bir fonksiyon gövdesinin içinde mi dışında mı durduğudur. Derleyici, kısa bildirimi yalnız bir fonksiyon gövdesinin içinde geçerli bir ifade sayıyor; paket düzeyinde beklediği şey var ya da const gibi bir bildirim başlığıdır, := değil.

Bu sınır, kısa bildirimin neden “kolay yol” değil, bağlama özgü bir kısaltma olduğunu gösteriyor. Kısa bildirim, bir fonksiyonun içinde art arda birçok değişken tanımlarken var sözcüğünü ve tip adını tekrar tekrar yazmaktan kurtarıyor; paket düzeyinde ise bu kısaltmaya gerek yok, çünkü paket düzeyindeki bildirimler zaten çoğunlukla tek tek, var ya da const ile yazılıyor ve bunların ilklendirme sırası dosya sırasına değil, bağımlılık sırasına göre çözülüyor — bu çözüm bir sonraki derste temel tipler üzerinden tekrar görülecek.

Bu satırın reddi, ilk dersteki dört retten farklı bir sınıfa ait. Orada reddedilen dört kaynak sözdizimi açısından geçerliydi, yalnız derleyici belirli bir kalıbı istemiyordu. Burada x := 5 satırı paket düzeyinde sözdizimi açısından geçersizdir — dilin grameri, := işaretini yalnız bir ifade bağlamında tanıyor, paket düzeyi bir ifade bağlamı değil, bir bildirim listesidir. Bu, “reddedildi” sınıfının kendi içinde iki alt türü olduğunu gösteriyor: bir kalıp gramer açısından geçerli ama derleyici tarafından istenmiyor olabilir (ilk dersteki dört ret), ya da kalıbın kendisi o bağlamda hiç tanımlı değildir (bu dersteki ret). İkisi de aynı derlenmedi yanıtını üretiyor, ama biri bir kural ihlali, öbürü bir gramer uyumsuzluğu.

Bu ayrımın pratik sonucu, paket düzeyinde bir değer tanımlamak istendiğinde yazım biçiminin değişmesi gerektiğidir — kısa bildirimden vara geçmek bir tercih değil, bir zorunluluktur. Bir fonksiyonun içinden paket düzeyine taşınan bir satır, yalnız yerini değiştirmekle kalmaz; yazımını da değiştirmek zorunda kalır.

Bu dersin üç ölçümü — kısa bildirimin tip çıkarımı, iotanın sabit doldurması, kısa bildirimin paket düzeyindeki reddi — aynı sorunun üç ayrı yüzü. Üçünde de yazılmayan bir şey var: bir tip adı, dört sabit değeri, bir bildirim biçiminin geçerliliği. Üçünde de dil, bu boşluğu rastgele bırakmıyor: birinde değere bakarak dolduruyor, birinde bir sayaç ve bir tekrar kuralıyla dolduruyor, birinde ise dolduracak hiçbir şey bulamayıp reddediyor. Kursun ilk iki dersi “nelerin reddedildiğini” ve “nelerin zorunlu olduğunu” ölçtü; bu ders, reddedilmeyen ve zorunlu da olmayan bir ara bölgeyi — çıkarımı — ölçüyor.

Özet

  • Kısa bildirim (:=), bir değişkenin tipini kaynakta hiç yazmadan, atanan değerin kendi tipinden çıkarır.
  • var ad tip = değer yazımı, çıkarımın sonucunu yazan tarafın elle belirlemesine izin verir; bu bir dönüşüm değil, bir ilk değer atamasıdır.
  • Altı sabitlik bir iota dizisinde yalnız ikisinin değeri kaynakta yazılıdır; dördü, satırın kendi iota değeriyle bir önceki ifadenin tekrarından üretilir.
  • iota, bir const bloğunun tamamında tek bir sayaçtır ve ikinci bir ifade göründüğünde sıfırlanmaz.
  • Aynı kısa bildirim satırı, bir fonksiyon gövdesinin içinde derlenirken paket düzeyinde reddedilir — kısa bildirim yalnız fonksiyon içinde geçerli bir ifadedir.

Sonraki Adım

Bu derste bir değişkenin tipi hep bir değerden çıkarıldı — 42den int, 3.14ten float64. Ama bu tiplerin kendileri hiç sorgulanmadı: bir tam sayı taştığında ne oluyor, iki kayan noktalı değer karşılaştırıldığında ne bekleniyor, ve rune denen tip aslında neyin karşılığı? Sıradaki ders temel tipleri, her birinin yazılmayan bir durumda ne yaptığını ölçerek ele alıyor.

İ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