---
title: 'Programlama Temelleri'
source: 'https://academia.sh/tr/kurslar/programlama-temelleri'
language: tr
updated: '2026-08-17T04:39:42+00:00'
license: 'CC BY-SA 4.0'
lessons: 18
outcomes:
    - 'Değişken, denetim akışı, fonksiyon ve veri tipi kavramlarını dilden bağımsız kullanır'
    - 'Bir problemi sözde kodla çözümleyip programa çevirir'
    - 'Özyinelemeli bir çözümün taban ve indirgeme adımlarını doğru kurar'
---

# Programlama Temelleri

Değişken, denetim akışı, fonksiyon ve özyineleme kavramlarını belirli bir dile bağlı kalmadan kuran, önkoşulsuz başlangıç kursu.

## Program, Değer ve Değişken

Programın ne olduğu, değerlerin adlara bağlanması ve temel veri tipleriyle ifade kurma.

### Program Nedir

Source: https://academia.sh/tr/kurslar/programlama-temelleri/program-nedir

Girdi–işlem–çıktı modeli, yürütme sırası, deyim ile ifade ayrımı ve hata türleri.

Önceki kurs, bir bilgisayarın komutları nasıl yürüttüğünü kurdu: işlemci bellekteki bit
örüntülerini getirir, çözer ve yürütür. Bu kurs aynı olaya öbür uçtan bakar — komutları
üreten metni yazan kişinin bakışından.

Bu dersin sorusu şudur: bir program neyi tanımlar ve hangi parçalardan oluşur?

#### Girdi, İşlem, Çıktı

Bir program, üç bileşenli bir dönüşümü tanımlar:

- **Girdi:** İşlenecek veri. Klavyeden, dosyadan, ağdan veya doğrudan programın içinden
  gelebilir.
- **İşlem:** Girdiden çıktıya giden adımlar.
- **Çıktı:** Üretilen sonuç. Ekrana yazılabilir, dosyaya kaydedilebilir ya da başka bir
  programa aktarılabilir.

Bu kursta baştan sona aynı somut problem kullanılacaktır: **bir ölçüm dizisinin
ortalamasını ve en büyük değerini bulmak.** Problem sade olduğu için her yeni kavram,
tanıdık bir zemin üzerinde tanıtılabilir.

Problemin bileşenleri şunlardır: girdi bir sayı dizisidir, işlem toplama ve
karşılaştırmadır, çıktı iki sayıdır.

#### Yürütme Sırası

Program metni yukarıdan aşağı okunur ve satırlar yazıldıkları sırayla yürütülür. Bu,
ilk bakışta belirtmeye değmeyecek kadar açık görünür; ancak iki sonucu vardır.

Birincisi, bir değer kullanılmadan önce üretilmiş olmalıdır. İkincisi, aynı program aynı
girdiyle her çalıştırıldığında aynı çıktıyı verir. Bu özellik **belirlilik**
olarak adlandırılır ve programların sınanabilir olmasının temelidir.

Belirliliğin bir koşulu vardır: programın davranışı yalnızca girdisine bağlı olmalıdır.
Zamanı okuyan, rastgele sayı üreten veya ağdan veri çeken bir program, aynı çağrıda
farklı sonuç verebilir. Bu tür kaynaklar programın sınanmasını zorlaştırdığı için
sınırlanır ve kodun geri kalanından ayrı tutulur; yazılım kalitesi müfredatı bu ayrımı
test edilebilirlik başlığı altında ele alır.

Sıra, yalnızca denetim akışı yapılarıyla değiştirilir — koşullar ve döngüler bir sonraki
konunun konusudur. Önceki kursta bunun donanımdaki karşılığı görülmüştü: program sayacı
her adımda bir sonraki komuta ilerler, dallanma komutları ise ona başka bir adres yazar.

#### İlk Program

Aşağıdaki program, iki ölçümün ortalamasını hesaplar:

```python
# Girdi: iki ölçüm
olcum_1 = 12
olcum_2 = 18

# İşlem: toplam ve ortalama
toplam = olcum_1 + olcum_2
ortalama = toplam / 2

# Çıktı
print(ortalama)          # 15.0
```

Beş satırın her biri bir **deyimdir (statement)**: yürütüldüğünde bir iş yapar. İlk
dört deyim bir değer üretip saklar, sonuncusu ekrana yazar.

`#` ile başlayan satırlar **yorumdur**; yürütülmez. Yorumlar okuyucu içindir ve kodun
ne yaptığını değil, **neden** öyle yaptığını anlatmaya ayrılmalıdır. Kodun ne yaptığı,
zaten kodun kendisinde yazılıdır.

Bu kursta örnekler Python ile verilecektir. Ancak anlatılan kavramlar — değişken,
koşul, döngü, fonksiyon — belirli bir dile ait değildir; her genel amaçlı dilde
karşılıkları vardır. Sözdizim değişir, kavram kalır.

#### Programı Çalıştırmak

Program metni bir dosyada durur. Dosyanın çalıştırılması, önceki kursta tanımlanan
zinciri başlatır: metin çevrilir, komutlar üretilir ve işlemci onları yürütür. Bu
zinciri başlatmanın iki yolu vardır.

**Dosya olarak çalıştırma**, kaynak dosyanın adını dilin çalıştırıcısına vermektir.
Program baştan sona yürür ve biter. Üretilen çıktı, bu kursta `print` ile ekrana
yazılacaktır.

**Etkileşimli çalıştırma**, deyimleri tek tek yazıp sonucunu hemen görmektir. Öğrenme
sırasında yararlıdır: bir ifadenin ne ürettiği, program yazmadan denenebilir. Bu kurstaki
örneklerin çoğu her iki yolla da çalıştırılabilir.

Girdinin dışarıdan alınması da bir deyimdir:

```python
metin = input("Ölçüm: ")     # kullanıcıdan bir satır okur
print("Girilen:", metin)
```

`input` çağrısı programı durdurur, bir satır bekler ve okunan satırı **dizgi olarak**
döndürür. Sayı girildiğinde bile sonuç dizgidir; sayısal işlem yapılacaksa dönüştürülmesi
gerekir. Bu ayrıntı, bu konunun son dersinde ele alınacaktır.

Bu kursun örnekleri, tekrarlanabilir olması için girdiyi çoğunlukla doğrudan kodun içinde
verir. Gerçek programlarda girdi kaynağı değişir; girdi–işlem–çıktı yapısı değişmez.

#### Deyim ve İfade

İki kavramın ayrılması, sonraki derslerin tamamında işe yarar.

**İfade (expression)** bir değer üretir. `12 + 18` bir ifadedir; değeri $30$'dur.
`olcum_1` de bir ifadedir; değeri, o adın bağlı olduğu şeydir.

**Deyim (statement)** bir eylem gerçekleştirir. `toplam = olcum_1 + olcum_2` bir
deyimdir: sağdaki ifadeyi hesaplar ve sonucu bir ada bağlar.

Ayrımın pratik sonucu şudur: ifadeler birbirinin içine yerleştirilebilir, deyimler
yerleştirilemez.

```python
ortalama = (olcum_1 + olcum_2) / 2      # ifade içinde ifade: geçerli
```

Bir deyimin sonucu yoktur; bu yüzden `x = (y = 3)` yazımı birçok dilde geçersizdir.
Bazı diller atamayı da ifade sayar ve buna izin verir — bu, dil tasarımı kararıdır ve
tanınmış bir hata kaynağıdır: karşılaştırma yerine yanlışlıkla atama yazmak, geçerli
ama yanlış bir program üretir.

#### Hata Türleri

Programların üç ayrı biçimde başarısız olması, üç ayrı aşamaya karşılık gelir. Önceki
kursun derleme aşamaları dersi bu ayrımın kaynağını vermişti.

**Sözdizim hatası:** Metin, dilin dilbilgisine uymuyor. Program hiç çalışmaz.

```python
ortalama = (olcum_1 + olcum_2 / 2      # kapanmayan parantez
```

**Çalışma zamanı hatası:** Metin geçerli, ancak yürütme sırasında yapılamayan bir işlem
isteniyor.

```python
bolen = 0
ortalama = toplam / bolen              # sıfıra bölme
```

Bu hatalar programı çalışırken durdurur. Nerede durduğu, hata iletisiyle birlikte
bildirilir.

**Mantık hatası:** Program çalışır, hata vermez, ama yanlış sonuç üretir.

```python
ortalama = olcum_1 + olcum_2 / 2       # 12 + 9 = 21,0  — beklenen 15,0
```

Bu üçüncüsü en pahalısıdır, çünkü kendini göstermez. Yukarıdaki satır, işlem önceliği
nedeniyle bölmeyi önce yapar; sonuç sayısal olarak makuldür ve hiçbir uyarı üretmez.
Yalnızca beklenen değeri bilen biri fark eder.

Mantık hatalarına karşı tek savunma, sonucun bağımsız olarak sınanmasıdır: elle
hesaplanmış bir örnekle karşılaştırma, kenar durumların denenmesi ve testler. Yazılım
kalitesi müfredatı bu savunmanın sistematik biçimini ele alır.

#### Program ile Algoritma

Son bir ayrım: **algoritma**, problemi çözen adımların kendisidir; **program**, o
adımların belirli bir dilde yazılmış hâlidir. Aynı algoritma farklı dillerde farklı
programlar verir; aynı problem için farklı algoritmalar seçilebilir.

Bu ayrım pratikte şuna yarar: bir program çalışmıyorsa, sorunun çözümde mi yoksa
çözümün yazılışında mı olduğu ayrı ayrı sorulabilir. İki soru farklı yerlerde aranır.

Ortalama hesabı için algoritma tek cümledir: değerleri topla, sayılarına böl.
Programın hangi dilde yazıldığı bu cümleyi değiştirmez. Algoritmaların doğruluğu ve
maliyeti, bu müfredattaki ayrı bir kursun konusudur.

#### Özet

- Bir program girdi, işlem ve çıktı bileşenlerinden oluşan bir dönüşüm tanımlar.
- Deyimler yazıldıkları sırayla yürütülür; aynı girdi aynı çıktıyı verir.
- İfade bir değer üretir, deyim bir eylem gerçekleştirir; ifadeler iç içe konabilir.
- Yorumlar yürütülmez ve kodun neden öyle yazıldığını anlatmaya ayrılır.
- Sözdizim hatası programı hiç çalıştırmaz, çalışma zamanı hatası yürütmeyi durdurur,
  mantık hatası ise sessizce yanlış sonuç üretir.
- Algoritma çözümün kendisidir; program onun bir dildeki ifadesidir.

#### Sonraki Adım

Yukarıdaki programda `olcum_1` gibi adlar kullanıldı ama ne oldukları tanımlanmadı. Bir
ad bir değere nasıl bağlanır, bu bağ ne zaman değişir ve bellekte ne olur? Sonraki ders
değişken kavramını, önceki kursta kurulan bellek modeliyle birlikte ele alacak.

### Değişkenler ve Bağlama

Source: https://academia.sh/tr/kurslar/programlama-temelleri/degiskenler-ve-baglama

Adın değere bağlanması, atamanın anlamı, iki değişken modeli ve adlandırma kuralları.

Önceki derste `olcum_1 = 12` yazıldı ve bunun bir değer sakladığı söylendi. Bu ders o
cümlenin altını doldurur: ad ile değer arasındaki bağ nasıl kurulur, ne zaman değişir ve
bellekte neye karşılık gelir?

Soru göründüğünden daha derindir, çünkü diller bu bağı iki farklı biçimde modeller ve
aradaki fark, ilerideki derslerde gözlenebilir sonuçlar doğurur.

#### Atama Bir Bağlama İşlemidir

`olcum_1 = 12` deyimi iki adımda yürütülür:

1. Eşittir işaretinin **sağındaki ifade** değerlendirilir; bir değer üretilir.
2. Bu değer, soldaki **ada bağlanır**.

Sıra önemlidir. `toplam = toplam + 5` deyimi, matematikte anlamsız bir denklem gibi
görünür; program olarak ise açıktır: önce sağdaki ifade hesaplanır (eski değere $5$
eklenir), sonra sonuç aynı ada yeniden bağlanır.

Bu nedenle eşittir işareti burada "eşitlik" değil, "bağla" anlamındadır. Bazı diller
karışıklığı önlemek için `:=` gibi ayrı bir simge kullanır; eşitlik sınaması ise
genellikle `==` ile yazılır.

#### Değişken Ne Tutar: İki Model

Diller değişkeni iki farklı biçimde modeller. Fark, önceki kursta kurulan bellek
modeliyle birlikte düşünüldüğünde netleşir.

**Bellek gözü modeli.** Değişken, belirli bir tür için ayrılmış adlandırılmış bir bellek
alanıdır. Atama, o alanın içeriğini değiştirir. Değişkenin adresi ömrü boyunca sabittir;
değişen, içindeki bit örüntüsüdür. Alt seviyeli dillerin çoğu bu modeli kullanır.

**Ad–nesne bağı modeli.** Değerler bellekte bağımsız nesneler olarak durur; değişken ise
bu nesnelerden birine takılmış bir etikettir. Atama, nesnenin içeriğini değiştirmez;
etiketi başka bir nesneye taşır. Aynı nesneye birden çok etiket takılabilir.

İki modelin gözlenebilir farkı, iki değişkenin aynı veriyi paylaşıp paylaşmadığında
ortaya çıkar:

```python
a = [1, 2, 3]      # bir liste nesnesi; 'a' ona bağlandı
b = a              # 'b' aynı nesneye bağlandı; kopya yok
b.append(4)

print(a)           # [1, 2, 3, 4]  — 'a' üzerinden de görülüyor
print(a is b)      # True          — aynı nesne
print(a == b)      # True          — aynı içerik

c = [1, 2, 3, 4]
print(a is c)      # False         — farklı nesneler
print(a == c)      # True          — aynı içerik
```

`is` işleci **kimlik** sorar: iki ad aynı nesneye mi bağlı? `==` işleci **eşitlik**
sorar: içerikleri aynı mı? İki sorunun karıştırılması, paylaşılan veriyle çalışan
programların yaygın hata kaynağıdır.

Bu ayrım, fonksiyonlara argüman aktarımı dersinde tekrar karşınıza çıkacak; oradaki
"değer mi referans mı" tartışması, buradaki iki modelin doğrudan sonucudur.

Paylaşımı kırmanın yolu, açıkça kopya üretmektir. Kopyalamanın da iki derinliği vardır:
yalnızca dış kabuğu kopyalayan sığ kopya ve iç içe tüm yapıyı kopyalayan derin kopya.
Sığ kopyada iç nesneler hâlâ paylaşılır; bu, kopyalandığı sanılan verinin beklenmedik
biçimde değişmesinin yaygın nedenidir. Kopyalama maliyeti veri büyüdükçe arttığından,
paylaşmak ile kopyalamak arasındaki seçim bir tasarım kararıdır.

#### Yeniden Bağlama ve Sıra

Bir ad, program boyunca farklı değerlere bağlanabilir. Bu, sırayla ilgili bir tuzağı da
beraberinde getirir: iki değişkenin değerlerini takas etmek, doğrudan iki atamayla
yapılamaz.

```python
x = 3
y = 7

# Yanlış: ilk atamadan sonra x'in eski değeri kaybolur.
x = y
y = x
print(x, y)        # 7 7  — takas olmadı

# Doğru: geçici bir ad kullanılır.
x, y = 3, 7
gecici = x
x = y
y = gecici
print(x, y)        # 7 3
```

Bazı diller çoklu atamayı destekler ve takas tek satıra iner (`x, y = y, x`); bu
yazımda sağ taraf tümüyle değerlendirilip sonra bağlama yapıldığı için geçici ada gerek
kalmaz. Sözdizim kolaylığı, altta yatan kuralı değiştirmez.

#### Tanımlanmamış Ad

Bir ada, bağlanmadan önce erişilirse ne olur? Yanıt, dilin değişken modeline bağlıdır ve
bu, iki modelin ikinci gözlenebilir farkıdır.

Ad–nesne bağı modelinde ad henüz hiçbir nesneye bağlı değildir; erişim, açık bir hata
verir:

```python
# print(toplam)     -> çalışma zamanı hatası: 'toplam' tanımlı değil
toplam = 0          # önce bağlanır
print(toplam)       # 0
```

Bellek gözü modelinde ise değişken için yer ayrılmıştır ve o yerde bir bit örüntüsü
zaten vardır. Dil, ayrılan alanı sıfırlamıyorsa, okunan değer önceki kullanımdan kalan
anlamsız bir örüntüdür. Program çöker de çökmeyebilir de; her çalıştırmada farklı sonuç
verebilir.

İkinci durumun tehlikesi, hatanın belirtisiz olmasıdır. Bu yüzden bu modeli kullanan
diller ya ilk değer vermeyi zorunlu kılar, ya derleyici uyarısı üretir, ya da ayrılan
alanı otomatik sıfırlar.

Genel kural her iki modelde de aynıdır: **bir değişken, kullanılmadan önce anlamlı bir
değere bağlanır.** İlk değer atamasını bildirimle aynı satıra koymak, bu kuralı görünür
kılar.

#### Adlandırma

Adlar dilin kurallarına ve ekibin sözleşmelerine uyar.

**Kurallar** dile aittir ve zorunludur: hangi karakterlerle başlanabileceği, büyük–küçük
harf ayrımının olup olmadığı, hangi sözcüklerin ayrılmış olduğu. `if` veya `while` gibi
anahtar sözcükler değişken adı olarak kullanılamaz.

**Sözleşmeler** ekibe aittir ve okunabilirlik içindir:

- Ad, değişkenin **ne tuttuğunu** söyler: `toplam`, `olcum_sayisi`, `en_buyuk`.
- Tek harfli adlar yalnızca dar ve alışılmış bağlamlarda kullanılır (döngü sayacı gibi).
- Kısaltma, okuyucunun bildiği varsayılamayacak biçimdeyse açılır.
- Bir projede tek bir yazım düzeni kullanılır; aynı kavram iki farklı adla anılmaz.

Ad seçimi süs değildir. Kodun okunma sayısı yazılma sayısından fazladır; adlar, kodu
her okuyanın harcadığı çabayı belirler. Yazılım tasarımı müfredatında bu konu temiz kod
başlığı altında ayrıntılandırılır.

#### Sabitler

Bazı adlar programın çalışması boyunca aynı değere bağlı kalmalıdır: bir dönüşüm
katsayısı, bir üst sınır, bir yapılandırma değeri. Bunlara **sabit** denir.

Diller bunu iki biçimde ele alır. Kimi diller sabitliği zorlar: sabite yeniden atama
derleme hatasıdır. Kimi diller yalnızca sözleşmeye dayanır — ad büyük harflerle yazılır
ve değiştirilmemesi beklenir.

```python
OLCUM_SINIRI = 100        # sözleşme gereği sabit sayılır
```

Sabit kullanmanın yararı, aynı değerin kod içinde birden çok yerde yinelenmesini
önlemektir. Değer değiştiğinde tek bir satır güncellenir; kodun içine serpiştirilmiş
sayılar aranmaz. İkinci ve daha az fark edilen yararı, ada anlam yüklemesidir: koşulda
geçen `100` sayısı okuyucuya hiçbir şey söylemezken, `OLCUM_SINIRI` adı sınırın neyi
temsil ettiğini söyler.

#### Özet

- Atama önce sağdaki ifadeyi değerlendirir, sonra sonucu soldaki ada bağlar; eşittir
  işareti eşitlik değil bağlama anlamındadır.
- Diller değişkeni iki biçimde modeller: adlandırılmış bellek gözü veya nesneye takılmış
  etiket.
- Aynı nesneye birden çok ad bağlanabilir; `is` kimliği, `==` içerik eşitliğini sınar.
- Yeniden bağlama sırası önemlidir; takas için geçici bir ad veya çoklu atama gerekir.
- Adlandırma kuralları dile, sözleşmeler ekibe aittir; ad değişkenin ne tuttuğunu
  söylemelidir.
- Sabitler, yinelenen değerleri tek bir noktada toplar.

#### Sonraki Adım

Bu derste adlara sayılar ve listeler bağlandı; ikisinin farklı davrandığı görüldü. Peki
bir değerin türü tam olarak neyi belirler ve kaç tür vardır? Sonraki ders, temel veri
tiplerini ve her birinin hangi işlemleri desteklediğini ele alacak.

### Temel Veri Tipleri

Source: https://academia.sh/tr/kurslar/programlama-temelleri/temel-veri-tipleri

Tip kavramı, tam sayı, gerçel sayı, mantıksal değer, dizgi ve boş değer; statik ve dinamik tipleme.

Önceki derste bir ada sayı bağlandı, sonra liste bağlandı ve ikisi farklı davrandı.
Listenin `append` işlemi vardı, sayının yoktu. Bu farkın adı **tiptir**.

Bu ders, tip kavramını tanımlar ve her programlama dilinde karşılığı bulunan temel
tipleri tanıtır.

#### Tip Nedir

Bir **tip** iki şeyi birlikte belirtir:

1. Hangi değerlerin geçerli olduğunu — değer kümesi.
2. Bu değerler üzerinde hangi işlemlerin tanımlı olduğunu.

Tam sayı tipinin değer kümesi belirli bir aralıktaki tam sayılardır; işlemleri toplama,
çıkarma, karşılaştırma gibi işlemlerdir. Dizgi tipinin değerleri karakter dizileridir;
birleştirme ve arama işlemleri tanımlıdır, çıkarma tanımlı değildir.

Önceki kursun sonucu burada doğrudan işe yarar: bellekte yalnızca bit örüntüleri vardır
ve anlam, örüntüye uygulanan yorum kuralından gelir. **Tip, tam olarak o yorum
kuralıdır.** `0x41424344` örüntüsünün tam sayı mı, gerçel sayı mı yoksa metin mi olduğu
sorusunun programlama dilindeki yanıtı, o değerin tipidir.

#### Tam Sayılar

Tam sayı tipi, kesir kısmı olmayan sayıları taşır. Önceki kursta kurulan iki gerçek
burada geçerlidir: sabit genişlikli tam sayıların bir aralığı vardır ve aralık
aşıldığında taşma oluşur.

Diller bu konuda ayrışır. Kimi diller sabit genişlik kullanır ve taşmayı ya sarma ile ya
da hata ile karşılar. Kimi diller — Python bunlardan biridir — tam sayıları gerektiği
kadar büyütür; sınır, yalnızca bellektir.

İki bölme işlemi ayırt edilmelidir:

```python
print(7 / 2)        # 3.5   — gerçel bölme
print(7 // 2)       # 3     — tam bölme (aşağı yuvarlar)
print(7 % 2)        # 1     — kalan
print(-7 // 2)      # -4    — aşağı yuvarlama negatif tarafta da geçerli
```

Tam bölmenin yuvarlama yönü diller arasında farklılık gösterir: kimi diller sıfıra
doğru, kimi diller aşağı yuvarlar. Negatif sayılarla çalışan kodda bu ayrıntı sonucu
değiştirir.

#### Gerçel Sayılar

Gerçel sayı tipi, kesirli değerleri kayan noktalı gösterimle taşır. Önceki kursun
uyarısı burada da geçerlidir: değerler yaklaşıktır ve eşitlikle karşılaştırılmaz.

```python
print(0.1 + 0.2 == 0.3)                    # False
print(abs((0.1 + 0.2) - 0.3) < 1e-9)       # True  — tolerans ile karşılaştırma
```

Pratik kural: para gibi kesinlik gerektiren büyüklükler kayan noktalı tiplerle
tutulmaz; onluk tabanlı tipler veya en küçük birimi taşıyan tam sayılar kullanılır.

#### Mantıksal Değerler

Mantıksal tipin yalnızca iki değeri vardır: doğru ve yanlış. Bu değerler genellikle
doğrudan yazılmaz; karşılaştırmalardan üretilir.

```python
olcum = 12
print(olcum > 10)          # True
print(olcum == 10)         # False
```

Mantıksal değerler, bir sonraki konudaki koşullu dallanmanın girdisidir. Bir koşulun
"doğru olması" ifadesi, bu tipte bir değer üretmesi demektir.

Birçok dil, mantıksal olmayan değerleri de koşulda kullanılabilir sayar: sıfır, boş
dizgi ve boş koleksiyon yanlış; diğerleri doğru kabul edilir. Bu kolaylık, okunabilirliği
düşürebilir; koşulun neyi sınadığı açık yazıldığında kod daha az yanlış anlaşılır.

#### Dizgiler

Dizgi tipi, karakter dizilerini taşır. Önceki kursun karakter kodlamaları dersi bu
tipin altındaki ayrıntıyı vermişti: dizgi, kod noktalarının dizisidir ve bellekte bir
kodlamaya göre baytlanır.

```python
ad = "ölçüm"
print(len(ad))             # 5    — kod noktası sayısı
print(ad[0])               # ö    — dizin sıfırdan başlar
print(ad.upper())          # ÖLÇÜM
print("ölçüm" + " " + "1") # ölçüm 1
```

Dizin numaralandırmasının sıfırdan başlaması, çoğu dilde ortak bir sözleşmedir ve
adres aritmetiğinden gelir: dizin, başlangıçtan itibaren kaç eleman ilerlendiğini
söyler.

Birçok dilde dizgiler **değiştirilemezdir**: bir dizginin içeriği değiştirilemez, yalnızca
yeni bir dizgi üretilir. Bu, dizgi birleştirmenin döngü içinde pahalı olmasının
nedenidir; her birleştirme yeni bir nesne yaratır.

#### Koleksiyonlar

Tek bir değer yerine değer dizisi tutan tipler, kursun ortak probleminin girdisini
oluşturur:

```python
olcumler = [12, 18, 7, 25, 14]     # liste: sıralı, değiştirilebilir
print(len(olcumler))               # 5
print(olcumler[0], olcumler[-1])   # 12 14
print(sum(olcumler) / len(olcumler))   # 15.2
print(max(olcumler))               # 25
```

Bu ders koleksiyonları yalnızca kullanacak kadar tanıtır; iç yapıları, maliyetleri ve
seçim ölçütleri **Veri Yapıları** kursunun konusudur.

#### Değiştirilemez ve Değiştirilebilir Tipler

Tipler, değerlerinin sonradan değiştirilip değiştirilemediğine göre de ayrılır.

**Değiştirilemez (immutable)** bir değerin içeriği yaratıldıktan sonra değiştirilemez. Sayılar
ve çoğu dilde dizgiler bu sınıftadır. Değişiklik gibi görünen her işlem, aslında yeni bir
değer üretir.

**Değiştirilebilir (mutable)** bir değerin içeriği yerinde güncellenebilir. Listeler bu
sınıftadır.

```python
metin = "ölçüm"
yeni = metin.upper()
print(metin, yeni)          # ölçüm ÖLÇÜM  — özgün dizgi değişmedi

olcumler = [12, 18]
olcumler.append(7)
print(olcumler)             # [12, 18, 7]  — liste yerinde değişti
```

Ayrım, önceki dersteki paylaşım kuralıyla birleştiğinde önem kazanır. Aynı nesneye iki
ad bağlıysa ve nesne değiştirilebilir bir tipteyse, biri üzerinden yapılan değişiklik
diğerinden de görülür. Nesne değiştirilemezse böyle bir sürpriz olmaz: değişiklik yeni bir
nesne ürettiği için diğer ad eski değere bağlı kalır.

Bu nedenle paylaşılan veri, değiştirilebilir tiplerle çalışırken dikkat gerektirir;
değiştirilemez tipler ise paylaşıma güvenlidir. Aynı gözlem, fonksiyonlara argüman aktarımı
dersinde tekrar edilecektir.

#### Boş Değer

Çoğu dilde, "değer yok" durumunu belirten özel bir değer bulunur. Bu değer, henüz
hesaplanmamış veya bulunamamış bir sonucu ifade eder.

Kolaylığın bedeli, bu değerin beklenmedik yerlerde ortaya çıkabilmesidir: boş değer
üzerinde işlem yapmaya çalışan kod, çalışma zamanı hatası verir. Bazı diller bu riski
tip düzeyinde ele alır ve boş olabilecek değerleri ayrı bir tiple işaretler; böylece
denetimin yapılıp yapılmadığı derleme sırasında sınanabilir.

#### Tip Ne Zaman Denetlenir

Diller, tip uyumunu iki farklı zamanda sınar.

**Statik tipleme:** Tipler derleme sırasında bilinir ve denetlenir. Uyumsuz bir işlem,
program hiç çalışmadan yakalanır. Karşılığında, tiplerin bildirilmesi veya çıkarsanması
gerekir.

**Dinamik tipleme:** Tip bilgisi değerlerin üzerinde taşınır ve denetim çalışma anında
yapılır. Yazım daha kısadır; buna karşılık aynı hata, ilgili satır yürütülene kadar
görünmez.

```python
sayi = 5
metin = "5"
print(sayi + sayi)         # 10
print(metin + metin)       # 55   — dizgi birleştirme
# print(sayi + metin)      # çalışma zamanı hatası: tipler uyumsuz
```

Üçüncü satırdaki hata, dinamik tiplemede ancak o satır çalıştığında ortaya çıkar. Statik
tiplemede aynı ifade, çalıştırmadan önce reddedilirdi. Önceki kursun derleme aşamaları
dersinde kurulan ilke burada da geçerlidir: soru, hatanın **ne zaman** görüldüğüdür.

#### Özet

- Tip, geçerli değerler kümesini ve o değerler üzerinde tanımlı işlemleri birlikte
  belirtir; bit örüntüsüne uygulanan yorum kuralının programlama dilindeki adıdır.
- Tam sayılarda bölme iki türlüdür ve yuvarlama yönü diller arasında değişir.
- Gerçel sayılar yaklaşıktır; eşitlik yerine tolerans ile karşılaştırılır.
- Mantıksal değerler karşılaştırmalardan üretilir; boş değer ise yokluğu belirtir ve
  denetlenmediğinde çalışma zamanı hatası kaynağıdır.
- Dizgiler kod noktası dizileridir; değiştirilemez tiplerde her değişiklik yeni bir değer
  üretir, değiştirilebilir tiplerde içerik yerinde güncellenir.
- Statik tipleme hataları çalıştırmadan önce, dinamik tipleme çalışırken yakalar.

#### Sonraki Adım

Tipler tanımlandı; şimdi bu değerlerden ifade kurma sırası geldi. Sonraki ders,
aritmetik, karşılaştırma ve mantıksal işleçleri ve — önceki derste bir mantık
hatasının kaynağı olan — işlem önceliği kurallarını ele alacak.

### İşleçler ve İfadeler

Source: https://academia.sh/tr/kurslar/programlama-temelleri/islecler-ve-ifadeler

Aritmetik, karşılaştırma ve mantıksal işleçler; öncelik, birleşme yönü ve kısa devre değerlendirme.

İlk derste bir mantık hatası örneği verilmişti: `olcum_1 + olcum_2 / 2` ifadesi
beklenen ortalamayı vermiyordu. Hata, işleçlerin hangi sırayla uygulandığını bilmemekten
kaynaklanıyordu.

Bu ders o kuralları tanımlar. Kurallar dilden dile küçük farklarla tekrarlanır; ortak
olan, önceliğin matematikteki alışkanlıkla uyumlu olmasıdır.

#### Aritmetik İşleçler

| İşleç | Anlam | Örnek | Sonuç |
|---|---|---|---|
| `+` | toplama | `12 + 18` | 30 |
| `-` | çıkarma | `18 - 12` | 6 |
| `*` | çarpma | `6 * 7` | 42 |
| `/` | gerçel bölme | `7 / 2` | 3.5 |
| `//` | tam bölme | `7 // 2` | 3 |
| `%` | kalan | `7 % 2` | 1 |
| `**` | üs alma | `2 ** 10` | 1024 |

Kalan işleci, göründüğünden daha sık kullanılır: bir sayının çift olup olmadığı
(`n % 2 == 0`), bir sayacın belirli aralıklarla döngüye girmesi ve değerlerin sabit
sayıda kovaya dağıtılması hep bu işleçle yazılır.

#### Karşılaştırma İşleçleri

Karşılaştırma işleçleri mantıksal değer üretir: `==`, `!=`, `<`, `>`, `<=`, `>=`.

Eşitlik sınamasının `==` ile yazılması, atamanın `=` ile yazılmasından ayırt edilmek
içindir. İkisinin karıştırılması, bazı dillerde geçerli ama yanlış bir program üretir;
bu yüzden birçok dil atamayı koşul içinde yasaklar.

Önceki derste kurulan ayrım burada da geçerlidir: `==` içerik eşitliğini, `is`
(veya karşılığı) aynı nesne olup olmadığını sınar.

Bazı diller karşılaştırmaların zincirlenmesine izin verir:

```python
olcum = 12
print(0 < olcum < 100)          # True  — iki karşılaştırma ve mantıksal 've'
```

Bu yazım okunabilirdir ancak evrensel değildir; çoğu dilde aynı ifade beklenenden farklı
sonuç verir, çünkü önce `0 < olcum` hesaplanır ve sonucu `100` ile karşılaştırılır.

#### Mantıksal İşleçler

Üç mantıksal işleç, koşulları birleştirir:

| $a$ | $b$ | `a and b` | `a or b` | `not a` |
|---|---|---|---|---|
| yanlış | yanlış | yanlış | yanlış | doğru |
| yanlış | doğru | yanlış | doğru | doğru |
| doğru | yanlış | yanlış | doğru | yanlış |
| doğru | doğru | doğru | doğru | yanlış |

Tablo, önceki kurstaki bit düzeyi VE/VEYA tablolarının aynısıdır. Fark, bu işleçlerin
bitler üzerinde değil, tüm ifade üzerinde çalışmasıdır.

#### Kısa Devre Değerlendirme

Mantıksal işleçlerin ayırt edici bir davranışı vardır: sonuç belli olduğunda sağ taraf
**hiç değerlendirilmez**.

- `and` işlecinde sol taraf yanlışsa, sonuç yanlıştır; sağ tarafa bakılmaz.
- `or` işlecinde sol taraf doğruysa, sonuç doğrudur; sağ tarafa bakılmaz.

Bu, yalnızca bir hız eniyilemesi değil, bir programlama aracıdır: sağ tarafın
çalışabilmesi için gereken koşul, sola yazılarak güvenceye alınır.

```python
bolen = 0

# Sol taraf yanlış olduğu için bölme hiç yapılmaz; hata oluşmaz.
if bolen != 0 and 10 / bolen > 1:
    print("büyük")
else:
    print("koşul sağlanmadı")      # koşul sağlanmadı

olcumler = []

# Sol taraf doğru olduğu için ilk elemana erişilmez.
if len(olcumler) == 0 or olcumler[0] > 10:
    print("boş veya ilk ölçüm büyük")   # boş veya ilk ölçüm büyük
```

İki koşulun sırası değiştirilirse programlar çalışma zamanı hatası verir. Koşul sırası,
bu nedenle üslup değil doğruluk meselesidir.

#### Öncelik ve Birleşme Yönü

**Öncelik**, farklı işleçlerden hangisinin önce uygulanacağını; **birleşme yönü**, aynı
öncelikteki işleçlerin hangi yönde gruplanacağını belirler.

Yüksekten alçağa doğru sık kullanılan öncelik sırası:

| Sıra | İşleçler |
|---|---|
| 1 | `()` gruplama |
| 2 | `**` üs alma |
| 3 | tekil `-` (işaret) |
| 4 | `*`, `/`, `//`, `%` |
| 5 | `+`, `-` |
| 6 | karşılaştırmalar |
| 7 | `not` |
| 8 | `and` |
| 9 | `or` |

```python
print(2 + 3 * 4)          # 14   — çarpma önce
print((2 + 3) * 4)        # 20   — gruplama önceliği değiştirir
print(2 ** 3 ** 2)        # 512  — üs alma sağdan sola: 2 ** (3 ** 2)
print(-2 ** 2)            # -4   — üs alma tekil eksiden önce: -(2 ** 2)
print(10 - 3 - 2)         # 5    — çıkarma soldan sağa: (10 - 3) - 2
print(True or False and False)   # True — 'and' önce
```

İlk dersteki mantık hatası bu tabloyla açıklanır: `olcum_1 + olcum_2 / 2` ifadesinde
bölme dördüncü sırada, toplama beşinci sıradadır; bölme önce yapılır.

Pratik kural: öncelik kurallarını bilmek gerekir, ancak okuyucunun da bilmesine
güvenmemek gerekir. Karışıklık ihtimali olan yerde parantez kullanmak, kuralı bilmenin
yerine geçmez — onu görünür kılar.

#### Bileşik Atama

Bir değişkenin kendi değeri üzerinden güncellenmesi o kadar sık yazılır ki, diller bunun
için kısa bir yazım sunar:

```python
toplam = 0
toplam = toplam + 12       # açık yazım
toplam += 18               # bileşik atama: aynı işlem
print(toplam)              # 30

sayac = 10
sayac -= 1                 # 9
sayac *= 2                 # 18
sayac //= 5                # 3
print(sayac)               # 3
```

Bileşik atama bir işleç değil, bir yazım kısaltmasıdır: `a += b` ifadesi `a = a + b`
anlamına gelir. Kısaltmanın iki yararı vardır — değişken adı bir kez yazılır (yanlış ada
atama olasılığı düşer) ve okuyucu, güncellemenin aynı değişken üzerinde olduğunu tek
bakışta görür.

Değiştirilebilir tiplerde bu eşdeğerlik incelir: bazı dillerde bileşik atama yeni bir
nesne üretmek yerine var olan nesneyi yerinde günceller. Paylaşılan bir listede bu fark
gözlenebilir sonuçlar doğurur; ayrım, değiştirilemezlik konusunda kurulan kuralın uzantısıdır.

Bir uyarı: bileşik atama da bir deyimdir, ifade değildir. Değeri yoktur; başka bir
ifadenin içine yerleştirilemez.

#### Aynı İşleç, Farklı Anlam

Bir işlecin anlamı, uygulandığı tiplere bağlıdır:

```python
print(3 + 4)              # 7        — sayısal toplama
print("3" + "4")          # 34       — dizgi birleştirme
print([1, 2] + [3])       # [1, 2, 3] — liste birleştirme
print("ab" * 3)           # ababab   — yineleme
```

Aynı simgenin farklı tiplerde farklı işlem yapması **işleç aşırı yükleme** olarak
adlandırılır. Kolaylık sağlar; buna karşılık, tipi yanlış varsayan kod sessizce beklenmedik
sonuç üretebilir. Kullanıcıdan alınan bir değerin sayı mı dizgi mi olduğu bilinmeden
yapılan `+` işlemi, bunun tipik örneğidir.

Bu, sonraki dersin konusuna doğrudan bağlanır: değerin tipi beklenenden farklıysa ne
yapılır?

#### Özet

- Aritmetik işleçler arasında tam bölme ve kalan, sayısal problemlerde sık kullanılır.
- Karşılaştırma işleçleri mantıksal değer üretir; `==` içerik eşitliğini sınar, atama
  değildir.
- `and`, `or` ve `not` koşulları birleştirir; doğruluk tabloları bit düzeyi
  karşılıklarıyla aynıdır.
- Kısa devre değerlendirmede sonuç belli olduğunda sağ taraf çalıştırılmaz; koşul sırası
  doğruluğu etkiler.
- Öncelik hangi işlecin önce uygulanacağını, birleşme yönü aynı öncelikteki işleçlerin
  gruplanmasını belirler; üs alma sağdan sola birleşir.
- Aynı işleç farklı tiplerde farklı anlam taşıyabilir.

#### Sonraki Adım

Bir ifadede farklı tipler karşılaştığında ne olur? Sayıya dizgi eklenebilir mi, gerçel
sayı tam sayıya nasıl çevrilir ve bu çevirilerde ne kaybedilir? Sonraki ders tip
dönüşümünü ve kayıplı dönüşümün sonuçlarını ele alacak.

### Tip Dönüşümü

Source: https://academia.sh/tr/kurslar/programlama-temelleri/tip-donusumu

Açık ve örtük dönüşüm, kayıplı daraltma, dönüşüm hataları ve girdi doğrulama.

Önceki ders, aynı işlecin farklı tiplerde farklı anlam taşıdığını gösterdi. Geriye bir
soru kaldı: bir ifadede farklı tipler yan yana geldiğinde ne olur?

Yanıt üç seçenekten biridir: dil dönüşümü kendiliğinden yapar, programcının açıkça
istemesini bekler, ya da işlemi reddeder. Bu dersin konusu bu üç davranış ve
dönüşümlerin bedelidir.

#### Açık Dönüşüm

**Açık dönüşümde** programcı, hedef tipi belirterek dönüşümü ister:

```python
print(int("12") + 1)          # 13     — dizgiden tam sayıya
print(float("3.5") * 2)       # 7.0    — dizgiden gerçel sayıya
print(str(12) + "1")          # 121    — tam sayıdan dizgiye
print(int(3.9))               # 3      — gerçel sayıdan tam sayıya
```

Açık dönüşümün üstünlüğü görünür olmasıdır: kodu okuyan, dönüşümün burada yapıldığını
ve hangi yöne gittiğini bilir.

Dördüncü satır dikkat ister. Gerçel sayıdan tam sayıya dönüşüm **yuvarlamaz, keser**:
kesir kısmı atılır.

```python
print(int(3.9), int(-3.9))    # 3 -3   — sıfıra doğru kesme
print(round(3.9))             # 4      — yuvarlama ayrı bir işlemdir
print(round(2.5), round(3.5)) # 2 4    — yarımlar en yakın çift sayıya
```

Son satır, sayısal kodda bilinmesi gereken bir ayrıntıdır: yarım değerlerin hep yukarı
yuvarlanması evrensel değildir. Yaygın bir kural, yarımları en yakın çift sayıya
yuvarlamaktır; bu, çok sayıda yuvarlamanın toplamda yukarı kaymasını engeller.

#### Örtük Dönüşüm

**Örtük dönüşümde** dil, tipleri uyumlu hâle getirmek için kendiliğinden dönüşüm yapar:

```python
print(3 + 2.5)                # 5.5    — tam sayı, gerçel sayıya yükseltildi
print(type(3 + 2.5))          # <class 'float'>
```

Buradaki dönüşüm **genişletmedir**: tam sayı, gerçel sayı kümesinde temsil edilebilir.
Genişletme yönündeki örtük dönüşümler genellikle güvenlidir.

Diller, örtük dönüşüme ne kadar izin verdiklerine göre ayrışır. Kimi diller yalnızca
güvenli genişletmelere izin verir; kimileri neredeyse her tipi birbirine çevirir. İkinci
grupta `"3" + 4` gibi bir ifade hata vermez, sessizce bir sonuç üretir — ve bu sonucun ne
olduğu dile göre değişir. Kolaylık, hatanın sessizleşmesi pahasına gelir.

Python bu iki uçtan ilkine yakındır: sayılar arasında yükseltme yapar, ancak dizgi ile
sayıyı toplamayı reddeder ve hata verir.

#### Kayıplı Dönüşüm

Bazı dönüşümler bilgi kaybeder. Üç tipik durum:

**Kesir kaybı.** Gerçel sayıdan tam sayıya geçişte kesir kısmı atılır; geri dönüş, eski
değeri vermez.

**Aralık taşması.** Geniş bir tam sayı, dar bir tipe sığmayabilir. Sabit genişlikli
dillerde sonuç sarar; önceki kursun taşma dersinde bunun neden sessizce olduğu
görülmüştü.

**Kesinlik kaybı.** Büyük tam sayılar, gerçel sayı gösteriminin mantis genişliğini
aşabilir:

```python
buyuk = 10 ** 23
print(float(buyuk) == buyuk)      # False
print(int(float(buyuk)))          # 99999999999999991611392
```

Değer sessizce değişmiştir; hata iletisi yoktur. Kayan noktalı sayılar dersinde
belirtilen sınır burada gözlenir hâle gelir.

Pratik kural: daraltma yönündeki dönüşümler açıkça yazılır ve gerekçesi bilinir. Örtük
daraltmaya izin veren dillerde derleyici uyarıları açık tutulur.

#### Dönüşüm Hataları

Dönüşüm her zaman mümkün değildir. Dizgi bir sayıyı temsil etmiyorsa dönüşüm hata verir:

```python
# int("üç")   -> çalışma zamanı hatası: geçersiz sayı gösterimi
# int("3.5")  -> çalışma zamanı hatası: tam sayı gösterimi değil
print(int(float("3.5")))          # 3  — iki adımda: önce gerçel, sonra kesme
```

Bu, dış dünyadan gelen veriyle çalışırken kuraldır: kullanıcı girdisi, dosya içeriği ve
ağ yanıtı **doğrulanmadan dönüştürülmez**. İki yaygın kalıp vardır.

Birincisi, dönüşümü denemek ve hatayı yakalamak:

```python
def sayiya_cevir(metin: str, varsayilan: int = 0) -> int:
    """Metni tam sayıya çevirir; çevrilemiyorsa varsayılanı döndürür."""
    try:
        return int(metin)
    except ValueError:
        return varsayilan

print(sayiya_cevir("12"))         # 12
print(sayiya_cevir("üç"))         # 0
print(sayiya_cevir("üç", -1))     # -1
```

İkincisi, dönüştürmeden önce biçimi sınamak:

```python
metin = "12"
if metin.isdigit():
    olcum = int(metin)
    print(olcum + 1)              # 13
```

İki kalıptan hangisinin seçileceği, hatanın beklenen bir durum mu yoksa istisnai bir
durum mu olduğuna bağlıdır. Kullanıcı girdisinde geçersiz değer beklenen bir durumdur;
programın kendi ürettiği verinin geçersiz olması ise istisnaidir.

#### Girdiden Sayıya: Tam Örnek

Dış girdinin dizgi olarak gelmesi ve sayısal işlem için dönüştürülmesi gereği, bu
konunun tüm parçalarını bir araya getirir. Kursun ortak problemi üzerinden:

```python
# Girdi: her satırda bir ölçüm bulunan metin (dosyadan okunmuş gibi düşünün)
ham_girdi = ["12", "18", "7", "yirmi", "25", ""]

olcumler = []
atlanan = 0

for satir in ham_girdi:
    metin = satir.strip()              # baştaki ve sondaki boşlukları at
    try:
        olcumler.append(int(metin))    # geçerliyse sayıya çevir ve ekle
    except ValueError:
        atlanan += 1                   # değilse say ve geç

print(olcumler)                        # [12, 18, 7, 25]
print(atlanan)                         # 2
print(sum(olcumler) / len(olcumler))   # 15.5
print(max(olcumler))                   # 25
```

Program üç ayrı karar içerir ve üçü de bu konuda kurulan kavramlara dayanır: girdi
temizlenir (dizgi işlemi), dönüştürülür (açık dönüşüm) ve geçersiz değerler ayrı
sayılır (hata yakalama).

Dördüncü bir karar da vardır ve gözden kaçmaya elverişlidir: geçersiz satırların
atlanması mı, programın durdurulması mı, yoksa varsayılan bir değerle doldurulması mı
gerekir? Bu, dilin değil problemin sorusudur. Ölçüm verisinde eksik değerin nasıl ele
alınacağı, veri analizi müfredatının ayrı bir konusudur.

`for` döngüsünün nasıl çalıştığı sonraki konunun konusudur; burada yalnızca her satırın
sırayla işlendiği kadarıyla okunması yeterlidir.

#### Sessiz Tuzak: Sayı Gibi Görünen Dizgiler

Sayısal görünen bir değerin dizgi olarak kalması, hata vermeyen ama yanlış sonuç veren
bir duruma yol açar:

```python
print("12" < "9")             # True   — dizgi karşılaştırması: karakter karakter
print(12 < 9)                 # False  — sayısal karşılaştırma
print(sorted(["12", "9", "100"]))    # ['100', '12', '9']
print(sorted([12, 9, 100]))          # [9, 12, 100]
```

Aynı sınıfın ikinci örneği, sayıların yazımının bölgeye göre değişmesidir. Ondalık
ayracı kimi yazımda nokta, kimi yazımda virgüldür; binlik ayracı da benzer biçimde
değişir. Bir dilin standart dönüşüm işlevi genellikle tek bir yazımı kabul eder;
kullanıcıdan veya dış kaynaktan gelen metin bu yazıma uymuyorsa dönüşüm başarısız olur
ya da — daha kötüsü — sayının yalnızca bir bölümünü okur. Bu yüzden dış verinin hangi
yazımla geldiği bilinmeli, dönüşüm o yazıma göre yapılmalıdır.

Dizgiler karakter karakter karşılaştırılır; `"1"` karakteri `"9"` karakterinden önce
gelir. Dosyadan okunan ölçümler dönüştürülmeden sıralanırsa, sonuç sayısal sıra değil
sözlük sırası olur. Bu, birinci dersteki mantık hatası sınıfının tipik örneğidir:
program çalışır, hata vermez, yanlış sonuç verir.

#### Özet

- Açık dönüşümde hedef tip programcı tarafından belirtilir; örtük dönüşümü dil
  kendiliğinden yapar.
- Gerçel sayıdan tam sayıya dönüşüm yuvarlamaz, keser; yuvarlama ayrı bir işlemdir ve
  yarım değerlerin kuralı diller arasında değişebilir.
- Genişletme yönündeki dönüşümler güvenlidir; daraltma yönündekiler kesir, aralık veya
  kesinlik kaybedebilir ve bu kayıp sessizdir.
- Dizgiden sayıya dönüşüm başarısız olabilir; dış girdi doğrulanmadan dönüştürülmez.
- Sayı gibi görünen dizgiler karakter karakter karşılaştırılır ve sıralama sonucunu
  değiştirir.

#### Sonraki Adım

Bu konu, değerleri ve onlardan ifade kurmayı tamamladı. Ancak programlar yalnızca
yukarıdan aşağı akmaz: koşula göre dallanır ve tekrar eder. Sonraki konu, akışı
yönlendiren yapıları — koşullu dallanma ve döngüleri — ele alacak.

## Denetim Akışı

Koşullu dallanma, döngüler ve çözümün sözde kodla tasarlanması.

### Koşullu Dallanma

Source: https://academia.sh/tr/kurslar/programlama-temelleri/kosullu-dallanma

Koşula bağlı yürütme, çok yollu seçim, koşulların okunabilir yazımı ve eşleme tabanlı dağıtım.

Buraya kadar yazılan programlar baştan sona tek bir yolu izledi: her deyim, sırası
gelince bir kez yürütüldü. Oysa çoğu problem, duruma göre farklı davranış gerektirir.
Bir ölçüm sınırın üzerindeyse uyarı verilmeli, altındaysa sessiz kalınmalıdır.

Bu ders, akışın koşula göre ayrılmasını ele alır. Önceki kursta bunun donanımdaki
karşılığı görülmüştü: karşılaştırma komutu bayrakları kurar, koşullu dallanma komutu
program sayacını yalnızca koşul sağlanıyorsa değiştirir. Buradaki her yapı, sonunda o
komutlara indirgenir.

#### Tek Yollu ve İki Yollu Seçim

En basit biçim, bir bloğun yalnızca koşul sağlandığında yürütülmesidir:

```python
olcum = 25
SINIR = 20

if olcum > SINIR:
    print("sınır aşıldı")        # sınır aşıldı
```

Koşul, mantıksal bir değer üreten herhangi bir ifadedir. Sağlanmazsa blok atlanır ve
akış bloğun altından devam eder.

İki yollu seçimde, koşul sağlanmadığında yürütülecek bir blok da tanımlanır:

```python
if olcum > SINIR:
    durum = "yüksek"
else:
    durum = "normal"

print(durum)                     # yüksek
```

Blokların hangi deyimleri kapsadığı dile göre farklı gösterilir: kimi diller süslü
parantez kullanır, kimi diller anahtar sözcükle bloğu kapatır, Python girintiyi kullanır.
Gösterim değişir; anlam aynıdır — koşula bağlı olarak yürütülecek deyimler kümesi
işaretlenmiştir.

Girinti kullanan dillerde bir uyarı gerekir: girinti yalnızca biçim değil, anlamdır.
Bloğun içinde sanılan ama girintisi kaydırılmış bir deyim, koşuldan bağımsız olarak her
zaman çalışır. Bu, ilk derste tanımlanan mantık hatası sınıfının sık görülen bir
örneğidir.

#### Çok Yollu Seçim

İkiden fazla durum varsa koşullar zincirlenir. Zincirde **ilk sağlanan koşul kazanır**;
kalanlar hiç sınanmaz.

```python
def sinifla(olcum: int) -> str:
    if olcum < 10:
        return "düşük"
    elif olcum < 20:
        return "orta"
    elif olcum < 30:
        return "yüksek"
    else:
        return "aşırı"

print(sinifla(7), sinifla(12), sinifla(25), sinifla(40))
# düşük orta yüksek aşırı
```

Zincirin sırası, koşulların anlamını belirler. İkinci daldaki `olcum < 20` koşulu, tek
başına okunduğunda $5$ değeri için de doğrudur; ancak oraya ulaşıldığında ilk koşulun
sağlanmadığı bilinir, yani `olcum` en az $10$'dur. Her dal, kendinden öncekilerin
olumsuzlanmasını örtük olarak taşır.

Bu nedenle sıra değiştirildiğinde program bozulur. Aşağıdaki zincir, `10` ve üstündeki
**her** değer için "aşırı" üretir — "orta" beklenen `12` de dahil — çünkü ilk koşul en
başta duruyor ve kendisinden sonraki daha dar koşulu hiç bırakmıyor:

```python
def yanlis_sinifla(olcum: int) -> str:
    if olcum >= 10:              # geniş koşul en başta
        return "aşırı"
    elif olcum < 20:
        return "orta"
    else:
        return "düşük"

print(yanlis_sinifla(12))        # aşırı  — beklenen "orta"
print(yanlis_sinifla(25))        # aşırı  — bu değer için doğru
```

Pratik kural: koşullar, en dar durumdan en geniş duruma doğru sıralanır; en genel durum
`else` dalında toplanır. Zincirin sonunda `else` dalı yoksa, hiçbir koşulun sağlanmadığı
girdi sessizce hiçbir şey yapmadan geçer — bu, gözden kaçan durumların tipik saklandığı
yerdir. Her zincirin son dalı, beklenmeyen girdinin ne olacağını açıkça söylemelidir.

#### Koşulları Okunabilir Yazmak

Koşul karmaşıklaştıkça kodun okunabilirliği hızla düşer. Üç alışkanlık bunu önler.

**Mantıksal değeri doğrudan kullanmak.** `if kosul == True` yazımı gereksizdir; `kosul`
zaten mantıksal bir değerdir. Karşılaştırma eklemek, ifadeyi uzatır ve yanlışlıkla
atamaya dönüşme riski taşır.

**Koşula ad vermek.** Uzun bir koşul, anlamını söyleyen bir değişkene bağlanır:

```python
olcum, sicaklik = 25, 80

# Okunması güç:
if olcum > 20 and sicaklik > 75 and not (olcum > 40):
    print("uyarı")

# Adlandırılmış:
sinir_asildi = olcum > 20
asiri_sicak = sicaklik > 75
guvenli_aralikta = olcum <= 40

if sinir_asildi and asiri_sicak and guvenli_aralikta:
    print("uyarı")               # uyarı
```

**Olumsuzlamayı sadeleştirmek.** İç içe olumsuzlamalar, De Morgan kurallarıyla
düzleştirilir: `not (a and b)` ifadesi `(not a) or (not b)` ile eşdeğerdir. İki yazımdan
hangisinin seçileceği, hangisinin problemi daha doğrudan anlattığına bakılarak
kararlaştırılır.

#### Koşullu İfade

Koşullu dallanma bir deyimdir: bir eylem yapar, değer üretmez. Buna karşılık birçok dil,
koşula göre **değer üreten** bir biçim de sunar. İki yazımın farkı, ilk dersteki deyim–
ifade ayrımının doğrudan uygulamasıdır.

```python
olcum = 25

# Deyim biçimi: durum değişkeni iki dalda ayrı ayrı bağlanır.
if olcum > 20:
    durum = "yüksek"
else:
    durum = "normal"

# İfade biçimi: tek bağlama, koşul değeri seçer.
durum = "yüksek" if olcum > 20 else "normal"
print(durum)                     # yüksek
```

İfade biçimi, bir değerin iki seçenekten birine bağlandığı kısa durumlarda okunabilirliği
artırır: değişken bir kez yazılır ve bağlamanın tek bir yerde olduğu görülür.

Sınırı da vardır. Dallar uzunsa, iç içe geçmişse veya yan etki içeriyorsa ifade biçimi
okunmaz hâle gelir. Ölçüt şudur: koşul bir **değer** seçiyorsa ifade, bir **eylem**
seçiyorsa deyim biçimi kullanılır.

#### İç İçe Koşullar

Koşullar birbirinin içine yerleştirilebilir. Derinlik arttıkça okuma zorlaşır; her
düzey, okuyucunun akılda tutması gereken bir varsayım ekler.

Üç düzeyden derin koşul yığınları, genellikle iki ayrı sorunun aynı yerde çözülmeye
çalışıldığının işaretidir. Derinliği azaltmanın standart yolu, geçersiz durumları başta
eleyip erken çıkmaktır. Bu
yazım, fonksiyonlar konusunda **koruma cümlesi** adıyla tekrar ele alınacaktır; buradaki
karşılığı, iç içe geçmiş dalların düz bir zincire dönüştürülmesidir.

#### Eşleme Tabanlı Dağıtım

Koşul zinciri, tek bir değerin sabitlerle karşılaştırılmasından ibaretse, birçok dil
bunun için ayrı bir yapı sunar — anahtar temelli seçim veya desen eşleme. Yapının adı ve
yetenekleri dile göre değişir.

Zincire alternatif bir yaklaşım, seçimi veriye taşımaktır:

```python
BIRIM_CARPANI = {"mm": 0.001, "cm": 0.01, "m": 1.0}

def metreye_cevir(deger: float, birim: str) -> float:
    if birim not in BIRIM_CARPANI:
        raise ValueError(f"bilinmeyen birim: {birim}")
    return deger * BIRIM_CARPANI[birim]

print(metreye_cevir(25, "cm"))   # 0.25
print(metreye_cevir(1500, "mm")) # 1.5
```

Bu yazımın üstünlüğü, yeni bir birim eklemenin kod değil veri değişikliği olmasıdır.
Koşul zinciri her yeni durumda uzarken, eşleme tablosu aynı kalır. Karar sayısı arttıkça
bu fark belirginleşir; yazılım tasarımı müfredatında aynı fikir, koşulların
çok biçimlilikle değiştirilmesi başlığı altında genişletilir.

Seçim ölçütü şudur: durumlar sabit ve az sayıdaysa koşul zinciri açıktır; durumlar
büyüyorsa veya çalışma anında değişiyorsa eşleme tercih edilir.

#### Özet

- Koşullu dallanma, bir kod bloğunu yalnızca koşul sağlandığında yürütür; koşul
  mantıksal değer üreten bir ifadedir.
- Blok sınırı dile göre farklı gösterilir; girinti kullanan dillerde girinti anlamın
  parçasıdır.
- Çok yollu seçimde ilk sağlanan koşul kazanır; her dal, kendinden öncekilerin
  olumsuzlanmasını örtük taşır ve sıra değişimi programı bozar.
- Koşullar adlandırılarak ve olumsuzlamalar sadeleştirilerek okunabilir kılınır;
  mantıksal değer `== True` ile karşılaştırılmaz.
- Tek bir değerin sabitlerle karşılaştırıldığı durumlarda eşleme tablosu, uzayan koşul
  zincirinin yerini alabilir.

#### Sonraki Adım

Dallanma, akışın hangi yoldan gideceğini belirler ama her yolu yalnızca bir kez yürütür.
Ortak problemdeki ölçüm dizisi ise her eleman için aynı işi gerektirir. Sonraki ders,
aynı bloğun tekrar tekrar yürütülmesini sağlayan döngüleri ve döngünün sonlanma
güvencesini ele alacak.

### Döngüler

Source: https://academia.sh/tr/kurslar/programlama-temelleri/donguler

Koleksiyon üzerinde yineleme, sayaçlı ve koşullu döngüler, sonlanma güvencesi ve birikeç kalıbı.

Kursun ortak problemi bir ölçüm dizisinin ortalamasını ve en büyüğünü bulmaktı. Şimdiye
kadar bu, hazır işlevlerle yapıldı. Bu ders, aynı işi adım adım yapan yapıyı tanımlar:
**döngü**.

Döngü, bir kod bloğunun birden çok kez yürütülmesini sağlar. Kaç kez yürütüleceği ya
baştan bellidir ya da bir koşula bağlıdır; bu ayrım, döngü türlerini belirler.

#### Koleksiyon Üzerinde Yineleme

En sık kullanılan biçim, bir koleksiyonun elemanları üzerinde sırayla ilerlemektir:

```python
olcumler = [12, 18, 7, 25, 14]

for olcum in olcumler:
    print(olcum)
# 12 / 18 / 7 / 25 / 14   (her biri ayrı satırda)
```

Döngü değişkeni (`olcum`), her turda bir sonraki elemana bağlanır. Kaç eleman varsa o
kadar tur yapılır; sayaç tutmaya, uzunluk sorgulamaya veya sınır denetimine gerek yoktur.

Bu yazımın üstünlüğü, sınır hatalarını olanaksız kılmasıdır. Elle sayaç yönetilen
döngülerde en sık görülen hata, bir eleman eksik veya fazla işlemektir; koleksiyon
üzerinde doğrudan yineleme bu hata sınıfını ortadan kaldırır.

#### Sayaçlı Döngü

Bazı problemlerde elemanın kendisi değil, **sırası** gerekir: ilk üç ölçümü işlemek, iki
diziyi aynı anda gezmek veya belirli sayıda tekrar yapmak gibi.

```python
for i in range(5):
    print(i, end=" ")
print()                      # 0 1 2 3 4

for i in range(len(olcumler)):
    print(f"{i}. ölçüm: {olcumler[i]}")
# 0. ölçüm: 12 ... 4. ölçüm: 14
```

Sayma aralığının sıfırdan başlaması ve üst sınırın **dışarıda** kalması yaygın bir
sözleşmedir: `range(5)` beş değer üretir ve son değer $4$'tür. Bu düzenin yararı,
`range(len(dizi))` ifadesinin tam olarak geçerli dizinleri vermesidir.

Yalnızca sıra numarası gerekiyorsa sayaçlı döngü doğru araçtır. Hem sıra hem eleman
gerekiyorsa, çoğu dil ikisini birlikte veren bir yapı sunar; elemanı dizinle tekrar
aramak gereksiz iştir.

#### Koşullu Döngü

Tur sayısı önceden bilinmiyorsa koşullu döngü kullanılır. Koşul her turdan önce sınanır;
sağlanmadığında döngü biter.

```python
kalan = 100
tur = 0

while kalan > 1:
    kalan = kalan // 2       # her turda yarıya iner
    tur += 1

print(tur)                   # 6
```

Bu döngü, bir sayının kaç kez yarılanabileceğini bulur. Tur sayısı baştan bilinmez;
başlangıç değerine bağlıdır. Aynı hesap, önceki kurstaki ikili arama örneğinin
maliyetinin de kaynağıdır.

Koşullu döngünün bir varyantı, koşulu turun **sonunda** sınar; böylece gövde en az bir
kez çalışır. Kullanıcıdan geçerli bir değer alınana kadar sorma kalıbı bu biçimi
gerektirir.

#### Sonlanma Güvencesi

Döngünün duracağını garanti eden bir şey yoktur; bunu program yazan sağlar. Kural şudur:
**her turda, koşulu yanlışlamaya yaklaşan bir değişiklik olmalıdır.**

Yukarıdaki örnekte `kalan` her turda küçülür ve sıfıra doğru gider; sıfır ile bir
arasındaki değerler koşulu yanlışlar. Bu azalan büyüklük, döngünün sonlanma gerekçesidir.

Gerekçe kurulamıyorsa döngü sonsuza kadar sürebilir:

```python
kalan = 100
while kalan > 1:
    kalan = kalan // 2
    if kalan == 3:
        kalan = 100          # geriye sıçrama: azalma güvencesi bozuldu
```

Bu döngü hiç bitmez. Program çalışır durumda kalır, işlemciyi meşgul eder ve dışarıdan
durdurulmadıkça sonlanmaz. Sonsuz döngü, çalışma zamanı hatası üretmediği için ilk
derste tanımlanan mantık hatası sınıfına girer.

Bilinçli sonsuz döngüler de vardır: bir sunucu, istek beklerken kasıtlı olarak durmaz.
Bu durumda çıkış, döngü koşulundan değil, bir sonraki derste ele alınacak erken çıkış
deyimlerinden veya dış bir sinyalden gelir.

#### Birikeç Kalıbı

Döngülerin en yaygın kullanımı, tur boyunca bir değeri biriktirmektir. Kalıp üç adımdan
oluşur: birikeci başlat, her turda güncelle, döngüden sonra kullan.

```python
olcumler = [12, 18, 7, 25, 14]

toplam = 0                       # 1. başlangıç değeri
for olcum in olcumler:
    toplam += olcum              # 2. her turda güncelle

ortalama = toplam / len(olcumler)   # 3. sonucu kullan
print(toplam, ortalama)          # 76 15.2
```

Başlangıç değerinin seçimi, işlemin **etkisiz elemanıdır**: toplama için sıfır, çarpma
için birdir. Yanlış seçilen başlangıç değeri sessizce yanlış sonuç verir.

En büyük değeri bulmak da aynı kalıptır, ancak başlangıç değeri daha dikkat ister:

```python
en_buyuk = olcumler[0]           # ilk eleman başlangıç kabul edilir
for olcum in olcumler:
    if olcum > en_buyuk:
        en_buyuk = olcum

print(en_buyuk)                  # 25
```

Burada sıfırla başlamak yanlış olurdu: tüm ölçümler negatif olsaydı, sonuç yanlışlıkla
sıfır çıkardı. Doğru başlangıç, dizinin ilk elemanıdır — ve bu, dizinin **boş
olmadığını** varsayar. Boş girdi, bu tür kodun standart kenar durumudur ve döngüden önce
ele alınmalıdır.

Kursun ortak problemi böylece hazır işlev kullanmadan çözülmüş oldu: iki döngü, iki
birikeç.

#### Döngü Değişmezi

Sonlanma, döngü hakkındaki iki sorudan yalnızca biridir. İkincisi doğruluktur: döngü
bittiğinde sonuç neden doğrudur?

Yanıt **döngü değişmezi** kavramıyla verilir: her turun başında ve sonunda doğru kalan
bir ifade. Birikeç döngüsünün değişmezi şudur — *`toplam`, o ana kadar gezilen
elemanların toplamıdır.*

Değişmez üç yerde sınanır. Döngüye girmeden önce doğrudur (hiç eleman gezilmemiştir,
toplam sıfırdır). Her tur onu korur (bir eleman gezilir, aynı eleman toplama eklenir).
Döngü bittiğinde ise tüm elemanlar gezilmiştir; değişmez, `toplam` değerinin tüm
elemanların toplamı olduğunu söyler. İstenen sonuç budur.

Bu üç adım, döngünün doğruluğunun kanıtıdır ve tek tek turları izlemeden kurulur.
En büyük değeri bulan döngünün değişmezi de aynı biçimde yazılır: *`en_buyuk`, o ana
kadar gezilen elemanların en büyüğüdür.* Bu ifadeyi yazmak, başlangıç değerinin neden
ilk eleman olması gerektiğini de açıklar — sıfırla başlanırsa değişmez, döngüye
girmeden yanlıştır.

Değişmez yazma alışkanlığı, algoritmaların doğruluk kanıtlarının temelidir ve
**Algoritmalar** kursunda biçimselleştirilir.

#### İç İçe Döngüler

Bir döngünün gövdesi başka bir döngü içerebilir. Dış döngünün her turu, iç döngünün
tamamını çalıştırır; toplam tur sayısı, ikisinin çarpımıdır.

```python
for i in range(3):
    for j in range(2):
        print(i, j, end="  ")
print()
# 0 0  0 1  1 0  1 1  2 0  2 1
```

Çarpımsal büyüme, iç içe döngülerin başlıca maliyetidir: her yeni düzey, iş miktarını
kat kat artırır. Bu gözlem, algoritma karmaşıklığı kavramının çıkış noktasıdır ve
**Algoritmalar** kursunda biçimselleştirilir. Bu kurs için yeterli olan sezgi şudur:
veri büyüdükçe, iç içe döngülerin maliyeti tek düzeyli döngülerden çok daha hızlı artar.

#### Özet

- Koleksiyon üzerinde yineleme, elemanları sırayla işler ve sınır hatalarını ortadan
  kaldırır.
- Sayaçlı döngü, elemanın kendisi değil sırası gerektiğinde kullanılır (sayma sıfırdan
  başlar, üst sınır dışarıda kalır); koşullu döngü ise tur sayısı önceden
  bilinmediğinde kullanılır.
- Bir döngünün sonlanması, her turda koşulu yanlışlamaya yaklaşan bir değişiklikle
  güvenceye alınır; bu değişiklik yoksa döngü sonsuza dek sürer.
- Birikeç kalıbı üç adımdır: başlat, her turda güncelle, sonra kullan; başlangıç değeri
  işlemin etkisiz elemanıdır ve en büyük değer aramada ilk elemandır.
- Döngü değişmezi, her turda doğru kalan ifadedir ve döngünün doğruluğunu turları
  izlemeden gerekçelendirir.
- İç içe döngülerde toplam tur sayısı çarpımsal büyür.

#### Sonraki Adım

Döngüler baştan sona akar, ancak bazen turun ortasında karar vermek gerekir: aranan
bulunduğunda erken çıkmak veya geçersiz bir elemanı atlayıp devam etmek gibi. Sonraki
ders, döngü akışını içeriden yönlendiren deyimleri ve bunların okunabilirlik üzerindeki
etkisini ele alacak.

### Döngü Denetimi

Source: https://academia.sh/tr/kurslar/programlama-temelleri/dongu-denetimi

Erken çıkış, turu atlama, arama kalıbı ve iç içe döngülerden çıkışta okunabilirlik.

Önceki ders döngüleri baştan sona akan yapılar olarak tanıttı: koşul sınanır, gövde
çalışır, tekrar edilir. Bazı problemler bu düzeni bozmayı gerektirir. Aranan değer
bulunduğunda kalan elemanlara bakmak gereksizdir; geçersiz bir eleman ise işlenmeden
atlanmalıdır.

Bu ders, döngü akışını içeriden yönlendiren deyimleri ve bunların okunabilirlik
üzerindeki etkisini ele alır.

#### Erken Çıkış

Erken çıkış deyimi, döngüyü koşulu beklemeden sonlandırır. Akış, döngünün hemen
altından devam eder.

```python
olcumler = [12, 18, 7, 25, 14]
SINIR = 20

for olcum in olcumler:
    if olcum > SINIR:
        print("sınırı aşan ilk ölçüm:", olcum)   # 25
        break
```

Erken çıkışın gerekçesi yalnızca hız değildir. `break` olmadan yazılan bu döngü, sınırı
aşan **son** ölçümü bulurdu; aranan ilk ölçüm olduğunda çıkış anlamın parçasıdır.

Sonsuz döngüler de çıkışını buradan alır: koşul her zaman doğru yazılır, gövdenin içinde
bir noktada çıkış kararı verilir. Bu yazım, çıkış koşulu turun ortasında hesaplanıyorsa
tercih edilir.

#### Turu Atlama

İkinci deyim, mevcut turu erkenden bitirir ve bir sonrakine geçer. Döngü sonlanmaz.

```python
ham = [12, -1, 18, -1, 25]

toplam = 0
sayilan = 0
for olcum in ham:
    if olcum < 0:            # geçersiz ölçüm işareti
        continue
    toplam += olcum
    sayilan += 1

print(toplam, sayilan)       # 55 3
```

Aynı iş, gövdeyi bir koşul bloğuna alarak da yazılabilir. İki yazım arasındaki seçim
girinti derinliğine bakılarak yapılır: atlama deyimi, geçersiz durumları başta eleyip
gövdenin geri kalanını düz tutar. Gövde uzunsa bu belirgin bir kazançtır.

#### Arama Kalıbı ve Bulunamama Durumu

Erken çıkışın en yaygın kullanımı aramadır. Aramanın gizli sorusu her zaman aynıdır:
**aranan bulunamazsa ne olur?**

```python
def ilk_asani_bul(olcumler: list[int], sinir: int) -> int | None:
    """Sınırı aşan ilk ölçümü döndürür; yoksa None döndürür."""
    for olcum in olcumler:
        if olcum > sinir:
            return olcum
    return None              # döngü hiç çıkmadan bittiyse: bulunamadı

print(ilk_asani_bul([12, 18, 7, 25, 14], 20))    # 25
print(ilk_asani_bul([12, 18, 7], 20))            # None
```

Son satırdaki `return None`, döngünün tükendiği durumu ele alır. Bu satır unutulursa
fonksiyon sessizce boş değer döndürür ve çağıran taraf, aramanın başarısız olduğunu
anlamak yerine beklenmedik bir değerle karşılaşır.

Bulunamama durumunu bildirmenin üç yolu vardır ve seçim, çağıranın ne yapacağına
bağlıdır: özel bir değer döndürmek, bir hata fırlatmak veya "bulundu mu" bilgisini
sonuçla birlikte döndürmek. İlk seçenek en yaygın olanıdır; ancak dönen değerin
denetlenmesi zorunludur, aksi hâlde temel veri tipleri dersinde tanımlanan boş değer
sorunu ortaya çıkar.

Bazı diller, döngü hiç erken çıkmadan bittiğinde çalışan ayrı bir blok sunar; aynı işi
bayrak değişkeni olmadan yazmayı sağlar. Bu yapı evrensel değildir.

#### Bayrak Değişkeni

Erken çıkışın bulunmadığı veya uygun olmadığı durumlarda, sonucu bir mantıksal değişkende
taşımak yaygın bir kalıptır:

```python
bulundu = False
for olcum in [12, 18, 7]:
    if olcum > 20:
        bulundu = True
        break

print(bulundu)               # False
```

Bayrak, döngü bittikten sonra hangi yoldan çıkıldığını söyler. Kalıp doğrudur ancak
maliyeti okunabilirliktir: okuyucu, bayrağın nerede kurulduğunu ve nerede okunduğunu
izlemek zorundadır. Arama bir fonksiyona ayrılabiliyorsa, doğrudan dönüş çoğu zaman daha
açıktır.

#### İç İçe Döngülerden Çıkış

Erken çıkış deyimi yalnızca **kendi döngüsünü** sonlandırır. İki düzeyli bir aramada iç
döngüden çıkmak, dış döngüyü durdurmaz.

```python
matris = [[1, 2, 3],
          [4, 5, 6],
          [7, 8, 9]]

def konum_bul(matris: list[list[int]], hedef: int) -> tuple[int, int] | None:
    """Hedefin (satır, sütun) konumunu döndürür; yoksa None."""
    for i, satir in enumerate(matris):
        for j, deger in enumerate(satir):
            if deger == hedef:
                return (i, j)      # her iki döngüden birden çıkar
    return None

print(konum_bul(matris, 6))        # (1, 2)
print(konum_bul(matris, 99))       # None
```

Üç seçenek vardır. **Fonksiyona ayırıp dönüş kullanmak**, yukarıdaki gibi, en okunur
olanıdır: tek bir deyim tüm düzeylerden çıkar. **Bayrak değişkeni** kullanmak, her düzeyde
ayrı bir çıkış koşulu gerektirir ve derinlik arttıkça hızla okunmaz hâle gelir. Bazı
diller **etiketli çıkış** sunar; hangi döngüden çıkılacağı açıkça yazılır.

Bu, fonksiyonların yalnızca tekrar kullanım için değil, **akışı sadeleştirmek** için de
kullanıldığının ilk örneğidir. Sonraki konu bu aracı ayrıntılı ele alacaktır.

#### Döngü Değişkenini Değiştirmek

Bir uyarı: sayaçlı döngünün değişkenini gövde içinde değiştirmek, birçok dilde beklenen
etkiyi yapmaz veya sonlanma güvencesini bozar. Aynı biçimde, üzerinde yineleme yapılan
bir koleksiyonu döngü sürerken değiştirmek — eleman eklemek veya silmek — tanımsız ya da
şaşırtıcı davranışa yol açar.

Güvenli kalıp, değişiklikleri ayrı bir koleksiyonda toplamak ve döngü bittikten sonra
uygulamaktır:

```python
olcumler = [12, -1, 18, -1, 25]

gecerli = [olcum for olcum in olcumler if olcum >= 0]   # yeni liste üretilir
print(gecerli)               # [12, 18, 25]
print(olcumler)              # [12, -1, 18, -1, 25]  — özgün liste değişmedi
```

Son örnekteki yazım, bir döngünün kısaltılmış biçimidir ve birçok dilde karşılığı
vardır. Süzme ve dönüştürme işlemlerini tek satıra indirir; koşul karmaşıklaştığında
açık döngü daha okunur kalır.

#### Erken Çıkış ve Tembel Yineleme

Erken çıkışın kazancı, işlenmeyen elemanların sayısıyla orantılıdır. Koleksiyon zaten
bellekte duruyorsa kazanç yalnızca zamandır. Ancak elemanlar **istendikçe üretiliyorsa**,
erken çıkış hiç üretilmeyen elemanların maliyetini de ortadan kaldırır.

Bu üretim biçimine tembel yineleme denir: değerler önceden hesaplanıp bir listede
toplanmaz, döngü her tur istedikçe hesaplanır.

```python
def olcum_kaynagi():
    """Değerleri istendikçe üretir; hepsi birden bellekte tutulmaz."""
    for taban in range(1, 1_000_000):
        yield taban * 3

for deger in olcum_kaynagi():
    if deger > 20:
        print("ilk aşan:", deger)     # ilk aşan: 21
        break
```

Döngü yedinci turda çıkar; geri kalan değerler hiç hesaplanmaz. Aynı iş, önce bir milyon
elemanlık liste kurup sonra aramakla da yapılabilirdi — sonuç aynı, maliyet
karşılaştırılamaz.

Ayrım büyük veri kaynaklarında belirleyicidir: bir dosyanın tamamını belleğe okumak ile
satır satır işlemek arasındaki fark, aynı fikrin uygulamasıdır.

#### Özet

- Erken çıkış döngüyü koşulu beklemeden sonlandırır; ilk eşleşmeyi arayan problemlerde
  anlamın parçasıdır.
- Turu atlama deyimi yalnızca mevcut turu bitirir ve geçersiz durumları başta eleyerek
  girinti derinliğini azaltır.
- Arama kalıbında bulunamama durumu açıkça ele alınır; dönen özel değer çağıran
  tarafından denetlenmelidir.
- Bayrak değişkeni erken çıkışın alternatifidir, ancak okuyucuya ek izleme yükü getirir.
- Erken çıkış yalnızca kendi döngüsünü sonlandırır; iç içe döngülerde en okunur çözüm,
  aramayı fonksiyona ayırıp dönüş kullanmaktır.
- Üzerinde yineleme yapılan koleksiyonu döngü sürerken değiştirmek güvenli değildir.

#### Sonraki Adım

Buraya kadar denetim yapıları doğrudan koda yazıldı. Karmaşık bir problemde ise önce
çözümün kendisi tasarlanır, sonra koda çevrilir. Sonraki ders, dilden bağımsız çözüm
yazımını — sözde kodu — ve bir çözümün koda dönüştürülmeden önce nasıl sınanacağını ele
alacak.

### Sözde Kod

Source: https://academia.sh/tr/kurslar/programlama-temelleri/sozde-kod

Çözümü dilden bağımsız yazma, elle izleme tablosuyla sınama ve kenar durumların belirlenmesi.

Önceki üç ders, denetim yapılarını doğrudan kod üzerinden tanıttı. Karmaşık bir problemde
ise sıra tersine döner: önce çözüm tasarlanır, sonra koda çevrilir. Tasarımı doğrudan
dilin sözdiziminde yapmak, iki farklı sorunu — "ne yapılacak" ve "nasıl yazılacak" —
aynı anda düşünmeyi gerektirir.

Bu ders, çözümü dilden bağımsız yazmanın ve koda dökmeden önce sınamanın yollarını
tanımlar.

#### Sözde Kod Nedir

**Sözde kod**, bir çözümü doğal dile yakın ama adımları belirsizlik bırakmayacak
kesinlikte yazma biçimidir. Derlenmez, çalıştırılmaz; okunur.

Katı bir sözdizimi yoktur, ancak yaygın sözleşmeler vardır:

- Atama için ok veya "olsun" ifadesi kullanılır.
- Bloklar girinti ile gösterilir.
- Döngü ve koşullar açık yazılır: *her ölçüm için*, *eğer … ise*.
- Dile özgü ayrıntılar — kütüphane adları, tip bildirimleri, bellek yönetimi — dışarıda
  bırakılır.

Ölçüsü şudur: sözde kodu okuyan iki kişi, aynı davranışı anlamalıdır — çözümün
tartışıldığı yer, kod yazılmadan önceki bu metindir. Belirsizlik
kalıyorsa yeterince ayrıntılı değildir; dilin sözdizimine yaklaşıyorsa gereğinden fazla
ayrıntılıdır.

#### Problemi Çözümlemek

Sözde kod yazmadan önce problemin anlaşılması gerekir. Dört soru bu işi yapar:

1. **Girdi nedir?** Türü, aralığı ve geçerlilik koşulları.
2. **Çıktı nedir?** Tek değer mi, koleksiyon mu, yan etki mi.
3. **Hangi durumlar özeldir?** Boş girdi, tek elemanlı girdi, eşit değerler, geçersiz
   değerler.
4. **Elle nasıl çözerdim?** Küçük bir örnek üzerinde adımları söze dökmek, çözümün
   kendisini ortaya çıkarır.

Dördüncü soru en verimlisidir. Beş ölçümün ortalamasını elle hesaplayan biri, farkında
olmadan bir birikeç kalıbı kullanır: sayıları sırayla toplar, sonra beşe böler.

#### Ortak Problemin Sözde Kodu

Kursun ortak problemi — ortalama ve en büyük değer — sözde kodla şöyle yazılır:

```
GİRDİ: ölçümler (sayı listesi)
ÇIKTI: ortalama (sayı), en_büyük (sayı)

eğer ölçümler boş ise
    hata bildir: "boş girdi"

toplam ← 0
en_büyük ← ölçümler[0]

her ölçüm için ölçümler içinde
    toplam ← toplam + ölçüm
    eğer ölçüm > en_büyük ise
        en_büyük ← ölçüm

ortalama ← toplam / ölçümlerin sayısı
ortalama ve en_büyük'ü döndür
```

Bu metinde hiçbir dile ait ayrıntı yoktur; buna karşılık her adım tektir ve tartışmaya
yer bırakmaz. Boş girdi denetiminin döngüden **önce** yapıldığına dikkat edin: `en_büyük`
değişkeninin ilk elemanla başlatılması, listenin boş olmadığını varsayar.

Aynı sözde kod, iki döngü yerine tek döngüde iki birikeç kullanır. Bu bir tasarım
kararıdır: veri bir kez gezilir, iki sonuç birlikte üretilir.

#### Elle İzleme

Çözümün doğruluğu, koda çevrilmeden önce **izleme tablosuyla** sınanabilir. Tabloda her
satır bir turu, her sütun bir değişkeni gösterir.

`[12, 18, 7]` girdisi için:

| Tur | ölçüm | toplam | en_büyük |
|---|---|---|---|
| başlangıç | — | 0 | 12 |
| 1 | 12 | 12 | 12 |
| 2 | 18 | 30 | 18 |
| 3 | 7 | 37 | 18 |
| sonuç | — | 37 | 18 |

Ortalama $37 / 3 \approx 12{,}33$; en büyük $18$. Sonuçlar elle hesaplananla uyuşur.

İzleme tablosu iki tür hatayı yakalar: yanlış başlangıç değeri ve yanlış güncelleme
sırası. Örneğin `en_büyük` sıfırla başlatılsaydı, tüm ölçümler negatif olan bir girdide
tablo sonucun sıfır çıktığını hemen gösterirdi.

Yöntem yavaştır ve büyük girdilerde uygulanamaz; amacı da bu değildir. Üç–dört elemanlık
bir girdi, mantık hatalarının çoğunu açığa çıkarmaya yeter. Aynı tablo, hata ayıklama
sırasında çalışan bir program üzerinde de kurulabilir: değişkenlerin her turdaki değerini
yazdırmak, izleme tablosunu programın kendisine hazırlatmaktır.

#### Kenar Durumlar

Bir çözümün olağan girdide çalışması yeterli değildir. Kenar durumlar, çözümün
varsayımlarının kırıldığı yerlerdir:

| Kenar durum | Bu problemdeki davranış |
|---|---|
| Boş liste | Ortalama tanımsız; hata bildirilir |
| Tek eleman | Ortalama o eleman, en büyük o eleman |
| Tüm değerler eşit | En büyük, o değer |
| Tüm değerler negatif | En büyük, en az negatif olan |
| Çok büyük değerler | Toplam, tip aralığını aşabilir |

Son satır, önceki kursun taşma dersine bağlanır: sabit genişlikli bir tipte biriken
toplam, aralık dışına çıkarsa sessizce yanlış sonuç verir.

Kenar durumların listelenmesi, sonradan yazılacak testlerin de listesidir. Yazılım
kalitesi müfredatında bu, sınır değer analizi olarak adlandırılır.

#### Problemi Alt Problemlere Bölmek

Ortak problem tek bir sözde kod bloğuna sığdı. Gerçek problemler sığmaz; çözüm, tek
parça hâlinde hem yazılamaz hem izlenemez.

Standart yöntem **ayrıştırmadır**: problem, her biri tek bir işi yapan alt problemlere
bölünür. Ölçüm verisiyle çalışan bir program şöyle ayrışabilir:

```
oku(kaynak) → ham satırlar
ayıkla(ham satırlar) → geçerli ölçümler, atlanan sayısı
özetle(ölçümler) → ortalama, en büyük
raporla(ortalama, en büyük, atlanan) → çıktı metni
```

Dört adımın her biri ayrı ayrı yazılabilir, ayrı ayrı sınanabilir ve biri değiştiğinde
diğerleri etkilenmez. Bölmenin ölçütü, adımın tek cümleyle anlatılabilmesidir: "ve"
bağlacıyla birleştirilen iki iş yapan bir adım, muhtemelen ikiye bölünmelidir.

Bu ayrıştırmanın koddaki karşılığı fonksiyonlardır. Sözde kodda bir adıma ad vermek,
kodda bir fonksiyon tanımlamaya karşılık gelir; bu nedenle ayrıştırma, kod yazılmadan
önce yapılan bir tasarım işidir.

#### Sözde Koddan Koda

Çeviri, sözde kodun her satırının hedef dildeki karşılığını yazmaktır:

```python
def ozetle(olcumler: list[int]) -> tuple[float, int]:
    """Ölçümlerin ortalamasını ve en büyüğünü döndürür."""
    if not olcumler:
        raise ValueError("boş girdi")

    toplam = 0
    en_buyuk = olcumler[0]

    for olcum in olcumler:
        toplam += olcum
        if olcum > en_buyuk:
            en_buyuk = olcum

    return toplam / len(olcumler), en_buyuk


print(ozetle([12, 18, 7]))          # (12.333333333333334, 18)
print(ozetle([12, 18, 7, 25, 14]))  # (15.2, 25)
```

İlk çıktı, izleme tablosunda elle hesaplanan değerlerle uyuşur — bu, çevirinin doğru
yapıldığının kanıtıdır.

Çeviri sırasında sözde kodda olmayan kararlar ortaya çıkar: hata nasıl bildirilecek, iki
değer nasıl birlikte döndürülecek, tipler ne olacak. Bu kararlar dile aittir ve sözde
kodun eksikliği değildir; sözde kodun **bilinçli olarak dışarıda bıraktığı** ayrıntılardır.

#### Özet

- Sözde kod, çözümü dilden bağımsız ve belirsizlik bırakmadan yazma biçimidir; ölçüsü
  iki okuyucunun aynı davranışı anlamasıdır.
- Problem çözümlemesi dört soruyla yapılır: girdi, çıktı, özel durumlar ve elle çözüm.
- İzleme tablosu, çözümü çalıştırmadan sınar; yanlış başlangıç değeri ve yanlış
  güncelleme sırası bu yolla yakalanır.
- Kenar durumların listesi, sonradan yazılacak testlerin listesidir.
- Sözde koddan koda çeviri sırasında ortaya çıkan kararlar dile aittir; sözde kod bu
  ayrıntıları bilinçli olarak dışarıda bırakır.

#### Sonraki Adım

Son örnekteki çözüm bir `def` bloğuyla sarmalandı ve `ozetle` adıyla anıldı. Bu yapı
şimdiye kadar açıklanmadan kullanıldı. Sonraki konu, fonksiyonları tanımlar: parametre
ve dönüş değeri nasıl çalışır, adlar hangi bölgede geçerlidir ve bir fonksiyon kendini
çağırdığında ne olur?

## Fonksiyonlar ve Soyutlama

Fonksiyon tanımı, argüman aktarımı, kapsam kuralları ve özyineleme.

### Fonksiyon Tanımı ve Çağrı

Source: https://academia.sh/tr/kurslar/programlama-temelleri/fonksiyon-tanimi-ve-cagri

Parametre ve argüman, dönüş değeri, çağrı yığıtı, koruma cümlesi ve iyi fonksiyonun ölçütleri.

Önceki konunun son dersi, problemi alt problemlere bölmenin çözüm tasarımının parçası
olduğunu söyledi ve bu bölmenin koddaki karşılığının fonksiyonlar olduğunu belirtti. Bu
ders o karşılığı tanımlar.

Fonksiyon, bir işi adlandırılmış ve yeniden çağrılabilir bir birime kapatır. Bu birim
iki yönlü bir sözleşmedir: çağıran ne vereceğini, fonksiyon ne döndüreceğini bilir.

#### Tanım ve Çağrı

Bir fonksiyon tanımı üç şey belirtir: adı, aldığı değerler ve gövdesi.

```python
def ortalama(olcumler: list[int]) -> float:
    """Ölçümlerin aritmetik ortalamasını döndürür."""
    return sum(olcumler) / len(olcumler)


print(ortalama([12, 18, 7]))        # 12.333333333333334
print(ortalama([10, 20]))           # 15.0
```

Tanımdaki `olcumler` bir **parametredir**: gövde içinde kullanılan, çağrıda değer
alacak ad. Çağrıdaki `[12, 18, 7]` ise bir **argümandır**: parametreye bağlanan gerçek
değer. İki terim sık karıştırılır; ayrım, "beklenen" ile "verilen" arasındadır.

Tanım yürütülmez, yalnızca fonksiyonu tanımlar. Gövdedeki deyimler ancak çağrıda çalışır.
Bu nedenle tanım içindeki bir hata, fonksiyon çağrılmadıkça ortaya çıkmayabilir.

#### Dönüş Değeri

`return` deyimi iki iş yapar: fonksiyonu sonlandırır ve bir değer üretir. Değer, çağrının
yerine geçer; bu nedenle çağrı bir **ifadedir** ve başka ifadelerin içine
yerleştirilebilir.

Bir fonksiyon birden çok değer döndürebilir; çoğu dilde bu, değerleri tek bir bileşik
değerde toplayarak yapılır:

```python
def ozetle(olcumler: list[int]) -> tuple[float, int]:
    """Ortalama ve en büyük değeri birlikte döndürür."""
    en_buyuk = olcumler[0]
    for olcum in olcumler:
        if olcum > en_buyuk:
            en_buyuk = olcum
    return ortalama(olcumler), en_buyuk


ort, enb = ozetle([12, 18, 7, 25, 14])
print(ort, enb)                     # 15.2 25
```

Hiçbir değer döndürmeyen fonksiyonlar da vardır; bunlar iş yapmak için çağrılır — ekrana
yazmak, dosyaya kaydetmek gibi. Bu ayrım, yazılım tasarımı müfredatında **komut–sorgu
ayrımı** olarak adlandırılır: bir fonksiyon ya durumu değiştirir ya değer döndürür,
ikisini birden yapması karışıklık üretir.

#### Varsayılan ve Adlandırılmış Argümanlar

Parametrelere varsayılan değer verilebilir; çağrıda belirtilmezse varsayılan kullanılır.

```python
def sinirlari_asanlar(olcumler: list[int], sinir: int = 20) -> list[int]:
    """Sınırı aşan ölçümleri döndürür."""
    return [olcum for olcum in olcumler if olcum > sinir]


print(sinirlari_asanlar([12, 18, 7, 25, 14]))            # [25]
print(sinirlari_asanlar([12, 18, 7, 25, 14], 10))        # [12, 18, 25, 14]
print(sinirlari_asanlar([12, 18, 7, 25, 14], sinir=15))  # [18, 25]
```

Son satırdaki yazım, argümanı adıyla verir. Bunun iki yararı vardır: sıra hatası
olanaksızlaşır ve çağrı okunduğunda değerin ne anlama geldiği görünür. Sayıların ve
mantıksal değerlerin argüman olarak geçtiği çağrılarda bu, okunabilirlik açısından
belirgin bir fark yaratır.

Bir uyarı: varsayılan değerin değiştirilebilir bir nesne olması, birçok dilde
şaşırtıcı davranışa yol açar; varsayılan yalnızca bir kez oluşturulur ve çağrılar
arasında paylaşılır. Değiştirilemez değerlerin varsayılan olarak seçilmesi bu tuzağı önler.

#### Çağrı Yığıtı

Önceki kursun son dersi, her çağrının yığıta bir çerçeve eklediğini kurmuştu. Bu ders o
mekanizmanın programcı tarafından görülen yüzüdür.

Bir çağrı yapıldığında akış fonksiyonun gövdesine geçer; dönüş adresi, argümanlar ve
yerel değişkenler çerçevede tutulur. `return` çalıştığında çerçeve bırakılır ve akış
çağrının yapıldığı yerden devam eder.

Çağrılar iç içe olduğunda çerçeveler üst üste birikir. `ozetle` fonksiyonu `ortalama`
fonksiyonunu çağırdığında, ikinci çerçeve birincinin üzerine eklenir; `ortalama`
döndüğünde yalnızca kendi çerçevesi bırakılır.

Bu yığının pratik karşılığı, hata iletileriyle birlikte gelen izleme dökümüdür: hangi
fonksiyonun hangi fonksiyondan çağrıldığı, en dıştan en içe doğru listelenir. Hata
ayıklarken okunacak ilk şey bu listedir; hatanın oluştuğu yer en alttaki satır, oraya
nasıl gelindiği ise yukarısıdır.

#### Ön Koşullar ve Koruma Cümlesi

Bir fonksiyonun düzgün çalışması için girdisinin sağlaması gereken koşullara **ön koşul**
denir. `ortalama` fonksiyonunun ön koşulu, listenin boş olmamasıdır; boş listede bölme
sıfıra bölmeye dönüşür.

Ön koşullar fonksiyonun **başında** ele alınır. Bu yazıma **koruma cümlesi (guard
clause)** denir:

```python
def ortalama(olcumler: list[int]) -> float:
    """Ölçümlerin ortalamasını döndürür. Liste boş olmamalıdır."""
    if not olcumler:                          # koruma cümlesi
        raise ValueError("ölçüm listesi boş")
    return sum(olcumler) / len(olcumler)
```

Koruma cümlesi, geçersiz durumu hemen eleyip asıl işi girintisiz bırakır. Alternatifi,
tüm gövdeyi bir `else` bloğuna almaktır; sonuç aynı, okunabilirlik farklıdır. Koşullu
dallanma dersinde söz verilen sadeleştirme budur.

İkinci karar, geçersiz girdide ne yapılacağıdır: hata fırlatmak, özel bir değer
döndürmek veya varsayılan bir sonuç üretmek. Seçim çağıranın ihtiyacına bağlıdır; kritik
olan, sözleşmenin belgelenmesi ve tutarlı uygulanmasıdır.

#### Sözleşmeyi Yazmak

Bir fonksiyonun sözleşmesi yalnızca imzasında değil, belgesinde de durur. Gövdenin
hemen başına yazılan açıklama, çağıranın bilmesi gereken her şeyi içerir:

- **Ne yapar** — tek cümle, eylem fiiliyle.
- **Parametreler** — anlamları, birimleri ve geçerli aralıkları.
- **Dönüş değeri** — neyi temsil ettiği.
- **Hata durumları** — hangi koşulda hangi hatanın üretildiği.
- **Yan etkiler** — varsa, açıkça.

```python
def sinirlari_asanlar(olcumler: list[int], sinir: int = 20) -> list[int]:
    """Sınırı aşan ölçümleri yeni bir listede döndürür.

    olcumler: ölçüm değerleri; değiştirilmez.
    sinir: karşılaştırma eşiği; varsayılan 20.
    dönüş: sınırdan büyük ölçümler, özgün sıralarıyla.
    """
    return [olcum for olcum in olcumler if olcum > sinir]
```

Belgenin kapsamı, fonksiyonun görünürlüğüyle orantılıdır: yalnızca kendi modülünde
kullanılan yardımcı bir fonksiyon tek satırla yetinebilirken, dışarıya açılan bir arayüz
tam sözleşmeyi hak eder.

Belgede yazılmayan her varsayım, çağıranın tahmin etmesi gereken bir ayrıntıya dönüşür.
Fonksiyonun listeyi değiştirip değiştirmediği bunun tipik örneğidir ve sonraki dersin
konusudur.

#### İyi Bir Fonksiyon

Fonksiyonun teknik tanımı basittir; iyi bir fonksiyonun ölçütleri ise deneyimle
oluşmuştur:

- **Tek bir iş yapar.** Adı "ve" ile kuruluyorsa (`hesapla_ve_yazdir`) muhtemelen ikiye
  bölünmelidir.
- **Adı ne yaptığını söyler.** Çağrıyı okuyan, gövdeyi okumadan ne olacağını bilmelidir.
- **Kısadır.** Uzunluk kuralı keskin değildir; ölçüt, gövdenin tek ekranda ve tek
  soluk hâlde okunabilmesidir.
- **Az sayıda parametre alır.** Parametre sayısı arttıkça çağrının doğru yazılma
  olasılığı düşer.
- **Sürprizsizdir.** Adının söylemediği bir yan etki üretmez.

Bu ölçütlerin tamamı, **Yazılım Tasarımı ve Mimari İlkeler** müfredatında gerekçeleriyle
ele alınır. Bu kurs için yeterli olan, ölçütlerin varlığını bilmek ve fonksiyon yazarken
sormaktır.

#### Özet

- Fonksiyon, bir işi adlandırılmış ve yeniden çağrılabilir bir birime kapatır; parametre
  beklenen adı, argüman verilen değeri belirtir.
- `return` fonksiyonu sonlandırır ve değer üretir; çağrı bir ifadedir ve başka
  ifadelerin içinde kullanılabilir.
- Varsayılan ve adlandırılmış argümanlar çağrıyı kısaltır ve okunur kılar; değiştirilebilir
  varsayılan değerler tuzaklıdır.
- Her çağrı yığıta bir çerçeve ekler; hata iletisindeki izleme dökümü bu yığının
  görüntüsüdür.
- Ön koşullar fonksiyonun başında koruma cümleleriyle ele alınır; geçersiz girdide ne
  yapılacağı sözleşmenin parçasıdır.

#### Sonraki Adım

Fonksiyona bir liste verildiğinde, fonksiyon içinde yapılan değişiklik çağıran tarafta
görünür mü? Yanıt dile ve değerin tipine bağlıdır ve bu, değişkenler dersinde tanıtılan
iki modelin doğrudan sonucudur. Sonraki ders argüman aktarımını ele alacak.

### Değer ve Referans ile Aktarım

Source: https://academia.sh/tr/kurslar/programlama-temelleri/deger-ve-referans-ile-aktarim

Argüman aktarım modelleri, fonksiyon içindeki değişikliğin dışarıdan görünürlüğü ve yan etki disiplini.

Bir fonksiyona liste verildiğinde, fonksiyon içinde o listeye eklenen eleman çağıran
tarafta görünür mü? Sorunun yanıtı "duruma göre" değildir; **kesin kurallara** bağlıdır
ve bu kurallar, değişkenler dersinde tanıtılan iki modelin doğrudan uzantısıdır.

Bu ders o kuralları tanımlar ve karışıklığın kaynağı olan üç terimi ayırır.

#### Üç Model

**Değerle aktarım (pass by value).** Argümanın bir **kopyası** parametreye bağlanır.
Fonksiyon içinde yapılan hiçbir değişiklik dışarıyı etkilemez. Büyük veriler için kopya
maliyeti vardır.

**Referansla aktarım (pass by reference).** Parametre, çağıranın değişkeninin **kendisine**
bağlanır. Fonksiyon parametreye yeni bir değer atarsa, çağıranın değişkeni de değişir.

**Nesne referansının değerle aktarımı.** Parametreye, nesnenin adresi **kopyalanarak**
bağlanır. Fonksiyon ile çağıran aynı nesneyi paylaşır; ancak parametreye yeni bir nesne
atamak, yalnızca yerel bağı değiştirir.

Üçüncü model en yaygın olanıdır ve en çok karıştırılanıdır. İki gözlenebilir sonucu
vardır ve bunlar birbirine karşıt görünür:

```python
def yerinde_ekle(liste: list[int]) -> None:
    """Verilen listeye eleman ekler: değişiklik dışarıdan görülür."""
    liste.append(99)

def yeniden_bagla(liste: list[int]) -> None:
    """Parametreye yeni bir liste atar: dışarısı etkilenmez."""
    liste = [0, 0, 0]

olcumler = [12, 18]

yerinde_ekle(olcumler)
print(olcumler)          # [12, 18, 99]  — nesne değişti

yeniden_bagla(olcumler)
print(olcumler)          # [12, 18, 99]  — bağ değişti, nesne değil
```

Fark tek cümlede özetlenir: **nesneyi değiştirmek dışarıdan görülür, adı yeniden bağlamak
görülmez.** Fonksiyon, çağıranın değişkenine değil, o değişkenin gösterdiği nesneye
erişir.

#### Değiştirilemez Tiplerde Fark Yok

Tipler dersinde kurulan ayrım burada işe yarar. Değiştirilemez bir değerin içeriği zaten
değiştirilemez; dolayısıyla ilk davranış olanaksızdır.

```python
def artir(sayi: int) -> int:
    sayi += 1            # yalnızca yerel bağ değişti
    return sayi

deger = 10
print(artir(deger), deger)      # 11 10
```

Sayılar, dizgiler ve benzeri değiştirilemez tipler bu nedenle her modelde aynı davranır.
Karışıklık yalnızca değiştirilebilir tiplerde ortaya çıkar.

#### Yan Etki

Bir fonksiyonun, dönüş değeri dışında dış dünyada bıraktığı ize **yan etki** denir.
Argümanı değiştirmek, küresel bir değişkeni güncellemek, ekrana yazmak, dosyaya yazmak —
hepsi yan etkidir.

Yan etki kendiliğinden kötü değildir; program sonunda bir iş yapmak zorundadır. Sorun,
yan etkinin **beklenmemesidir**. `ortalama` adlı bir fonksiyonun listeyi sıralaması,
çağıranın hesaba katmadığı bir değişiklik üretir.

Aynı işi iki farklı sözleşmeyle yazmak mümkündür:

```python
def sirali_kopya(olcumler: list[int]) -> list[int]:
    """Sıralanmış YENİ bir liste döndürür; girdi değişmez."""
    return sorted(olcumler)

def yerinde_sirala(olcumler: list[int]) -> None:
    """Verilen listeyi YERİNDE sıralar; değer döndürmez."""
    olcumler.sort()

a = [12, 18, 7]
b = sirali_kopya(a)
print(a, b)              # [12, 18, 7] [7, 12, 18]

yerinde_sirala(a)
print(a)                 # [7, 12, 18]
```

İki yazım da meşrudur. Ayırt edici olan, sözleşmenin adda ve belgede görünür olmasıdır.
Yerinde değiştiren fonksiyonlar genellikle değer döndürmez; bu, bir önceki dersteki
komut–sorgu ayrımının uygulanmış hâlidir. Değer döndüren bir çağrının aynı zamanda
girdiyi değiştirmesi, iki sözleşmeyi birbirine karıştırır.

#### Değişken Sayıda Argüman

Bazı fonksiyonların kaç argüman alacağı önceden belli değildir. Diller bunun için,
kalan argümanları tek bir koleksiyonda toplayan bir yazım sunar:

```python
def en_buyugu(*degerler: int) -> int:
    """Verilen değerlerin en büyüğünü döndürür. En az bir değer gerekir."""
    if not degerler:
        raise ValueError("en az bir değer gerekir")
    en_buyuk = degerler[0]
    for deger in degerler:
        if deger > en_buyuk:
            en_buyuk = deger
    return en_buyuk

print(en_buyugu(12, 18, 7))          # 18
print(en_buyugu(*[12, 18, 7, 25]))   # 25  — liste tek tek argümana açıldı
```

İkinci çağrıdaki yıldız, bir koleksiyonu ayrı argümanlara **açar**. Aynı simge, tanımda
argümanları toplar, çağrıda ise dağıtır; iki kullanım birbirinin tersidir.

Bu esnekliğin bedeli, sözleşmenin belirsizleşmesidir: argüman sayısı imzadan
okunamaz. Argümanların anlamı farklıysa — biri sınır, diğeri ölçüm listesi gibi — ayrı
parametreler her zaman daha açıktır. Değişken sayıda argüman, yalnızca **aynı türden ve
eşdeğer rolde** değerler için uygundur.

#### Savunma Kopyası

Bir fonksiyon, aldığı veriyi saklayacaksa ve çağıranın onu sonradan değiştirmesi sorun
yaratacaksa, kopyasını almalıdır. Bu, **savunma kopyası** olarak adlandırılır.

```python
class OlcumKaydi:
    def __init__(self, olcumler: list[int]) -> None:
        self.olcumler = list(olcumler)     # savunma kopyası

disaridaki = [12, 18]
kayit = OlcumKaydi(disaridaki)
disaridaki.append(99)                      # dışarıdaki liste değişti

print(kayit.olcumler)                      # [12, 18]  — kayıt etkilenmedi
```

Kopya alınmasaydı, kaydın içeriği dışarıdan sessizce değiştirilebilirdi. Buradaki `class`
yapısı bu kursta yalnızca örnek olarak kullanılmıştır; paradigmalar konusunda tanıtılacak.

Kopyanın bedeli bellek ve zamandır. Değişkenler dersinde belirtilen sığ–derin kopya
ayrımı burada da geçerlidir: iç içe yapılarda yalnızca dış kabuğu kopyalamak, paylaşımı
tam olarak kırmaz.

Aynı sorun ters yönde de vardır: bir fonksiyon, sakladığı koleksiyonu doğrudan
döndürüyorsa iç durumunu dışarı sızdırmış olur. Çağıran, dönen koleksiyonu değiştirerek
nesnenin içini kurallara uymadan güncelleyebilir. Bu, savunma kopyasının dönüş tarafındaki
karşılığıdır: dışarı verilen koleksiyon ya kopyalanır ya da değiştirilemez bir görünüm
olarak sunulur.

Üçüncü bir seçenek, veriyi değiştirilemez bir tiple taşımaktır. Böyle bir veri
kopyalanmak zorunda değildir; paylaşımı güvenlidir. Fonksiyonel programlama yaklaşımının bu tercihi,
paradigmalar konusunda ele alınacaktır.

#### Değiştirmeme Sözünü Tipe Yazmak

Bazı diller, bir parametrenin değiştirilmeyeceğini tip düzeyinde bildirmeye izin verir.
Bildirim yapıldığında, gövde içinde o değeri değiştiren bir işlem derleme hatası üretir.

Bunun iki kazancı vardır. Sözleşme belgede değil, imzada durur; okuyanın belgeye
başvurması gerekmez. Ve söz, kanıtlanmış olur — gelecekte gövdeye eklenen bir satır sözü
sessizce bozamaz.

Böyle bir bildirim sunmayan dillerde aynı söz, yalnızca belge ve inceleme disipliniyle
korunur. Bu durumda değiştirilemez tipleri tercih etmek, sözü uygulanabilir kılmanın en
güvenilir yoludur.

#### Hangi Modelin Kullanıldığını Anlamak

Bir dilin hangi modeli kullandığı, iki küçük denemeyle anlaşılır:

1. Fonksiyon içinde parametreye **yeni bir değer atayın**. Çağıranın değişkeni
   değişiyorsa referansla aktarım vardır.
2. Fonksiyon içinde nesneyi **yerinde değiştirin**. Çağıran tarafta görünüyorsa nesne
   paylaşılıyor demektir.

İkisi de görünmüyorsa değerle aktarım; yalnızca ikincisi görünüyorsa nesne referansının
değerle aktarımı söz konusudur. Bu deneme, belgeye başvurmadan modeli belirlemenin en
kısa yoludur.

#### Özet

- Değerle aktarımda kopya geçer, referansla aktarımda çağıranın değişkeninin kendisi
  geçer, nesne referansının değerle aktarımında adres kopyalanır ve nesne paylaşılır.
- En yaygın model üçüncüsüdür: nesneyi değiştirmek dışarıdan görülür, parametreyi
  yeniden bağlamak görülmez.
- Değiştirilemez tiplerde bu ayrım gözlenmez; karışıklık yalnızca değiştirilebilir tiplerde
  doğar.
- Yan etki, dönüş değeri dışındaki her izdir; sorun yan etkinin varlığı değil,
  beklenmemesidir.
- Yerinde değiştiren ve yeni değer üreten iki sözleşme de meşrudur; seçim adda ve
  belgede görünür kılınır.
- Saklanacak veri için savunma kopyası alınır; değiştirilemez tipler bu ihtiyacı ortadan
  kaldırır.

#### Sonraki Adım

Fonksiyon gövdesinde tanımlanan adlar dışarıdan görünmez; dışarıdaki adlar ise gövde
içinden okunabilir. Bu görünürlük kurallarının tam tanımı nedir ve bir ad birden çok
yerde tanımlıysa hangisi kazanır? Sonraki ders kapsam ve yaşam süresi kurallarını ele
alacak.

### Kapsam ve Yaşam Süresi

Source: https://academia.sh/tr/kurslar/programlama-temelleri/kapsam-ve-yasam-suresi

Ad arama sırası, gölgeleme, küresel duruma yazma, yerel değişkenlerin ömrü ve kapanışlar.

Önceki ders, fonksiyonun dışarıdaki nesneleri değiştirebildiğini gösterdi. Bu, doğal bir
soruyu doğurur: fonksiyon gövdesinde yazılan bir ad, hangi değişkeni gösterir? Gövdede
tanımlanan adlar dışarıdan görünür mü?

Bu dersin konusu **kapsam** — bir adın geçerli olduğu bölge — ve **yaşam süresi** — bir
değerin var olduğu zaman aralığıdır. İki kavram sık karıştırılır; biri görünürlükle,
diğeri ömürle ilgilidir.

#### Yerel Kapsam

Fonksiyon gövdesinde bağlanan adlar **yereldir**: yalnızca o gövde içinde görünür ve
fonksiyon döndüğünde ortadan kalkar.

```python
def ozetle(olcumler: list[int]) -> float:
    toplam = 0                     # yerel ad
    for olcum in olcumler:         # döngü değişkeni de yerel
        toplam += olcum
    return toplam / len(olcumler)

print(ozetle([12, 18]))            # 15.0
# print(toplam)                    -> hata: 'toplam' burada tanımlı değil
```

Bu, önceki kursun bellek düzeni dersinin doğrudan sonucudur: yerel değişkenler çağrı
çerçevesinde durur ve çerçeve, dönüşte bırakılır. Yerelliğin yararı yalıtımdır — iki
ayrı fonksiyon aynı adı, birbirini etkilemeden kullanabilir.

#### Ad Arama Sırası

Bir ad gövdede kullanıldığında, dil onu belirli bir sırayla arar. Yaygın sıra şudur:

1. **Yerel kapsam** — bu fonksiyonun gövdesi.
2. **Çevreleyen kapsam** — iç içe tanımlarda dıştaki fonksiyonun gövdesi.
3. **Küresel kapsam** — modülün en dış düzeyi.
4. **Yerleşik adlar** — dilin kendi sağladığı adlar.

Arama, ilk bulunan yerde durur. Bu nedenle bir ad hem yerelde hem küresel düzeyde
tanımlıysa, gövde içinde yerel olan kazanır.

```python
SINIR = 20                          # küresel

def asanlari_say(olcumler: list[int]) -> int:
    sayac = 0                       # yerel
    for olcum in olcumler:
        if olcum > SINIR:           # küresel addan okunur
            sayac += 1
    return sayac

print(asanlari_say([12, 25, 30]))   # 2
```

Küresel bir addan **okumak** serbesttir. Yazmak ise farklıdır ve bir sonraki bölümün
konusudur.

#### Gölgeleme

Dıştaki bir adla aynı adı yerelde kullanmaya **gölgeleme** denir. Dıştaki ad erişilemez
hâle gelir; yok olmaz, yalnızca o bölgede görünmez.

```python
SINIR = 20

def yanlis_kullanim(olcumler: list[int]) -> int:
    SINIR = 5                       # dıştaki adı gölgeler
    return len([o for o in olcumler if o > SINIR])

print(yanlis_kullanim([12, 25, 30]))   # 3   — beklenen 2 değil
print(SINIR)                            # 20  — küresel değer değişmedi
```

Gölgeleme her zaman hata değildir; kısa gövdelerde bilinçli olarak kullanılabilir. Hata
kaynağı olduğu durum, gölgelemenin **farkında olunmamasıdır**: okuyucu dıştaki değeri
sanırken kod yereli kullanır. Adlandırma dersindeki ölçüt burada da geçerlidir — ayrı
kavramlar ayrı adlarla anılır.

#### Küresel Duruma Yazmak

Bir fonksiyonun küresel değişkeni değiştirmesi, önceki dersteki yan etki sınıfına girer
ve genellikle kaçınılır. Çoğu dil bunu zorlaştırır: küresel bir ada yazmak için açık bir
bildirim gerekir; aksi hâlde atama yeni bir yerel ad oluşturur.

Kaçınmanın gerekçesi üç maddedir. Fonksiyonun davranışı yalnızca argümanlarına bağlı
olmaktan çıkar; aynı çağrı, küresel durumun geçmişine göre farklı sonuç verebilir.
Sınama zorlaşır, çünkü test her çalıştırmada durumu sıfırlamak zorundadır. Eşzamanlı
çalışan iki iş parçacığı aynı küresel değeri değiştirdiğinde, sonuç sıraya bağlı hâle
gelir.

Alternatif, gereken değeri **argüman olarak almak** ve sonucu **döndürmektir**. Bu,
fonksiyonu bağımsız ve sınanabilir kılar.

Sabitler bu kuralın dışındadır: değişmeyen küresel değerler paylaşılabilir ve
okunabilirliği artırır.

#### Yaşam Süresi ve Kapanışlar

Kapsam görünürlüğü, yaşam süresi ise ömrü belirtir. İkisi çoğu zaman örtüşür: yerel
değişken çerçeveyle birlikte gelir, çerçeveyle birlikte gider.

Örtüşmenin bozulduğu bir durum vardır. İç içe tanımlanan bir fonksiyon, dıştaki
fonksiyonun yerel değişkenine erişiyorsa, o değişken dıştaki fonksiyon döndükten sonra
da yaşamayı sürdürmek zorundadır. Bu yapıya **kapanış (closure)** denir.

```python
def sinir_denetleyicisi(sinir: int):
    """Verilen sınıra göre karar veren bir fonksiyon üretir."""
    def asiyor_mu(olcum: int) -> bool:
        return olcum > sinir        # dıştaki 'sinir' yakalandı
    return asiyor_mu

yirmi_asan = sinir_denetleyicisi(20)
on_asan = sinir_denetleyicisi(10)

print(yirmi_asan(25), yirmi_asan(15))   # True False
print(on_asan(15))                      # True
```

`sinir_denetleyicisi` çağrısı bittiğinde çerçevesi bırakılır, ancak `sinir` değeri
yaşamayı sürdürür: üretilen fonksiyon ona erişmektedir. Önceki kursun terimleriyle,
yakalanan değer yığıtta değil öbekte yaşar ve ona erişen fonksiyon var oldukça
toplanmaz.

Yakalanan değeri iç fonksiyondan **değiştirmek** ayrı bir bildirim gerektirir. Kural,
küresel adlarda olduğu gibidir: bildirim olmadan yapılan atama, dıştaki adı güncellemek
yerine yeni bir yerel ad oluşturur. Bu ayrım, sayaç tutan kapanışlarda sık karşılaşılan
bir hata kaynağıdır: `sayi = sayi + 1` yazan bir iç fonksiyon `sayi`'yı yerel saydığı için
sağdaki okuma da yerel ada gider ve çağrı, henüz değer atanmamış bir yerel adı okuma
hatasıyla durur.

Kapanışlar, davranışı parametreyle üretmenin sade bir yoludur; aynı fikir, nesneye
dayalı yaklaşımda durumu bir nesnede saklamakla karşılanır. İki yaklaşımın karşılaştırması
paradigmalar konusunda yapılacaktır.

#### Modüller ve Ad Alanları

Küresel kapsam sanıldığı kadar geniş değildir: her kaynak dosya kendi ad alanına
sahiptir. Bir dosyada tanımlanan ad, başka bir dosyada kendiliğinden görünmez; içe
aktarılması gerekir.

İçe aktarma, ada yeni bir bağ kurar. İki biçim vardır ve ikisinin okunabilirlik etkisi
farklıdır:

```python
import math                      # modül adı üzerinden erişim
print(math.sqrt(16))             # 4.0

from math import sqrt            # adı doğrudan kendi ad alanımıza al
print(sqrt(16))                  # 4.0
```

İlk biçimde `sqrt` adının nereden geldiği çağrıda görünür. İkinci biçimde çağrı kısalır,
ancak adın kaynağı görünmez olur ve aynı adı taşıyan iki kaynak birbirini gölgeleyebilir.

Ad alanları, çakışmaları önlemenin yapısal yoludur: iki farklı modül aynı adı, farklı
anlamlarla kullanabilir. Aynı fikrin daha büyük ölçeği paket ve ad alanı hiyerarşileridir
ve dil müfredatlarında ayrıntılandırılır.

Küresel kapsamla ilgili kural burada da geçerlidir: modül düzeyinde tanımlanan
değiştirilebilir durum, dosyanın her yerinden erişilebilir olduğu için aynı sınama ve
eşzamanlılık sorunlarını üretir.

#### Değişkenin Ömrünü Kısa Tutmak

Pratik bir kural, bu dersin tamamını özetler: **bir ad, gerektiği en dar bölgede
tanımlanır.** Döngü içinde kullanılan bir değişken döngü içinde, fonksiyona ait bir
değişken fonksiyon içinde tanımlanır.

Bu kural, kapsamı bir tasarım aracı hâline getirir. Dar kapsamın üç yararı vardır. Okuyucunun akılda tutması gereken ad sayısı azalır.
Adın nerede değiştirilebileceği sınırlanır, bu da hata aramayı kolaylaştırır. Ve
gölgeleme ile ad çakışması olasılığı düşer.

#### Özet

- Fonksiyon gövdesinde bağlanan adlar yereldir ve çağrı çerçevesiyle birlikte ortadan
  kalkar.
- Ad araması yerelden başlar, çevreleyen ve küresel kapsamlarla sürer, yerleşik adlarla
  biter; ilk bulunan kazanır.
- Gölgeleme, dıştaki adı erişilemez kılar; tehlikesi fark edilmemesidir.
- Küresel duruma yazmak fonksiyonu argümanlarından bağımsız hâle getirir ve sınamayı
  zorlaştırır; değer argümanla alınır, sonuç döndürülür.
- Kapanış, dıştaki yerel değişkeni yakalayan iç fonksiyondur; yakalanan değer, dış çağrı
  bittikten sonra da yaşar.
- Adlar gerektiği en dar bölgede tanımlanır.

#### Sonraki Adım

Bir fonksiyonun kendi kendini çağırması, çağrı yığıtında ilginç bir durum yaratır: aynı
fonksiyonun birden çok çerçevesi aynı anda var olur. Sonraki ders özyinelemeyi — taban
durumu, indirgeme adımı ve yığıt derinliği — ele alacak.

### Özyineleme

Source: https://academia.sh/tr/kurslar/programlama-temelleri/ozyineleme

Taban durumu ve indirgeme adımı, çağrı yığıtında açılım, sonsuz özyineleme ve maliyet.

Bir fonksiyon, gövdesinde başka bir fonksiyonu çağırabiliyorsa, kendisini de çağırabilir.
Bu, tanım gereği izin verilen bir durumdur ve **özyineleme (recursion)** olarak
adlandırılır.

İlk bakışta döngüsel görünen bu fikir, çağrı yığıtı bilindiğinde açıklığa kavuşur: her
çağrı kendi çerçevesine sahiptir, dolayısıyla aynı fonksiyonun birden çok çağrısı aynı
anda, birbirinden bağımsız değişkenlerle var olabilir.

#### İki Zorunlu Parça

Her özyinelemeli tanım iki parçadan oluşur:

**Taban durumu:** Özyineleme yapılmadan doğrudan yanıtlanan en küçük durum.

**İndirgeme adımı:** Problemi, aynı problemin daha küçük bir örneğine dönüştüren adım.

İkisinden biri eksikse çözüm çalışmaz. Taban durumu yoksa çağrılar sonsuza kadar sürer;
indirgeme problemi küçültmüyorsa taban duruma hiç ulaşılmaz.

```python
def faktoriyel(n: int) -> int:
    """n! değerini hesaplar. n negatif olmamalıdır."""
    if n < 0:
        raise ValueError("negatif değer")
    if n <= 1:              # taban durumu
        return 1
    return n * faktoriyel(n - 1)     # indirgeme: n, n-1'e iner

print(faktoriyel(5))        # 120
print(faktoriyel(0))        # 1
```

Taban durumunun `n <= 1` biçiminde yazılması, hem $0$ hem $1$ için doğru yanıtı verir ve
negatif olmayan her girdinin taban duruma ulaşmasını güvenceye alır.

#### Yığıtta Açılım

`faktoriyel(4)` çağrısının yürütülmesi, çerçevelerin üst üste birikmesi ve sonra ters
sırada çözülmesi biçiminde ilerler:

```
faktoriyel(4)
= 4 * faktoriyel(3)
= 4 * (3 * faktoriyel(2))
= 4 * (3 * (2 * faktoriyel(1)))
= 4 * (3 * (2 * 1))          ← taban duruma ulaşıldı
= 4 * (3 * 2)
= 4 * 6
= 24
```

Yukarı doğru giden satırlar çağrıların açılmasını, aşağı doğru inen satırlar dönüşlerin
toplanmasını gösterir. En derin noktada dört çerçeve aynı anda yığıttadır; her birinin
kendi `n` değeri vardır.

Bu, önceki kursta kurulan çerçeve modelinin en açık örneğidir: `n` değişkeni tek bir
bellek gözü değildir, her çağrının çerçevesinde ayrı ayrı bulunur.

#### Sonsuz Özyineleme

Taban durumu unutulduğunda veya indirgeme problemi küçültmediğinde, çağrılar birikir ve
yığıt tükenir.

```python
def hatali(n: int) -> int:
    return n * hatali(n - 1)     # taban durumu yok

# hatali(5)  -> özyineleme sınırı aşıldı hatası
```

Önceki kursun bellek düzeni dersinde bu davranış tanımlanmıştı: her çağrı bir çerçeve
ekler, hiçbiri dönmez ve yığıt için ayrılan alan tükenir. Çalışma zamanı koruması olan
dillerde denetimli bir hata alınır; olmayanlarda süreç sonlandırılır.

Hata iletisi genellikle aynı fonksiyon adını yüzlerce kez içerir — bu, sonsuz
özyinelemenin ayırt edici imzasıdır.

#### Doğal Özyinelemeli Problemler

Özyineleme, problemin kendisi özyinelemeli tanımlıysa doğal çözümdür. Ölçüm listesinin
toplamı böyle tanımlanabilir: *boş listenin toplamı sıfırdır; boş olmayan bir listenin
toplamı, ilk eleman artı geri kalanın toplamıdır.*

```python
def toplam(olcumler: list[int]) -> int:
    if not olcumler:                        # taban durumu: boş liste
        return 0
    return olcumler[0] + toplam(olcumler[1:])   # indirgeme: bir eleman eksil

print(toplam([12, 18, 7]))     # 37
print(toplam([]))              # 0
```

Bu çözüm, tanımın birebir çevirisidir; okunması kolaydır. Buna karşılık her çağrıda
listenin geri kalanını kopyalar ve eleman sayısı kadar çerçeve açar — aynı işi yapan bir
döngü, tek çerçeveyle ve kopyasız çalışır.

Özyinelemenin gerçekten üstün olduğu yerler, verinin kendisinin dallandığı
problemlerdir: ağaç yapılarının gezilmesi, dizinlerin alt dizinleriyle birlikte
taranması, böl ve yönet algoritmaları. Bu yapılar **Veri Yapıları** ve **Algoritmalar**
kurslarının konusudur; oradaki çözümlerin çoğu özyinelemeli yazılır.

#### Maliyet ve Tekrar Eden Hesap

Özyinelemenin iki maliyeti vardır: çerçeve başına bellek ve çağrı başına ek işlem. Üçüncü
ve daha sinsi bir maliyet, **aynı hesabın tekrarlanmasıdır**.

Fibonacci dizisinin doğrudan özyinelemeli tanımı bunun kanonik örneğidir:

```python
def fib(n: int) -> int:
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

print(fib(10))          # 55
```

`fib(5)` hesaplanırken `fib(3)` iki kez, `fib(2)` üç kez hesaplanır. Girdi büyüdükçe
tekrar sayısı üstel olarak artar; `fib(30)` için milyonun üzerinde çağrı yapılır.

Çözüm, hesaplanan değerleri saklamaktır:

```python
def fib_bellekli(n: int, bellek: dict[int, int] | None = None) -> int:
    if bellek is None:
        bellek = {}
    if n < 2:
        return n
    if n in bellek:                 # daha önce hesaplandıysa yeniden hesaplama
        return bellek[n]
    bellek[n] = fib_bellekli(n - 1, bellek) + fib_bellekli(n - 2, bellek)
    return bellek[n]

print(fib_bellekli(30))             # 832040
```

Not almanın önkoşulu, fonksiyonun aynı girdi için her zaman aynı sonucu vermesidir;
girdisi dışında bir duruma bağlı bir fonksiyonun sonucu saklanamaz. Bu tekniğe **not alma
(memoization)** denir ve dinamik programlamanın temelidir; ileri
algoritmalar kursunda genel biçimiyle ele alınır. Buradaki ders şudur: özyineleme
çözümün yapısını verir, ancak verimliliği kendiliğinden getirmez.

#### Karşılıklı Özyineleme

Özyineleme, fonksiyonun doğrudan kendini çağırmasıyla sınırlı değildir. İki fonksiyon
birbirini çağırarak da özyinelemeli bir yapı kurabilir; buna **karşılıklı özyineleme**
denir.

```python
def cift_mi(n: int) -> bool:
    if n == 0:
        return True
    return tek_mi(n - 1)

def tek_mi(n: int) -> bool:
    if n == 0:
        return False
    return cift_mi(n - 1)

print(cift_mi(4), tek_mi(4))     # True False
```

Bu örnek, kavramı göstermek içindir; aynı soru kalan işleciyle tek adımda yanıtlanır.
Karşılıklı özyinelemenin gerçek kullanım alanı, birbirine bağlı tanımların doğal olarak
karşılıklı olduğu yapılardır — dilbilgisi kurallarını işleyen ayrıştırıcılar bunun
tipik örneğidir: bir *ifade* tanımı *terim* tanımına, *terim* tanımı yeniden *ifade*
tanımına başvurur.

Taban durumu kuralı burada da geçerlidir, ancak sınanması güçleşir: sonlanma güvencesi
tek bir fonksiyonda değil, çağrı zincirinin tamamında aranır.

#### Özyineleme mi Döngü mü

Seçim ölçütleri:

| Ölçüt | Özyineleme | Döngü |
|---|---|---|
| Problem tanımı dallanıyor | Doğal | Elle yığıt yönetimi gerekir |
| Doğrusal ilerleme var | Çerçeve maliyeti öder | Daha ucuz |
| Derinlik girdiyle büyüyor | Yığıt sınırı riski | Sınır yok |
| Okunabilirlik | Tanıma yakın | Adımlara yakın |

Genel kural: **problem özyinelemeli tanımlanıyorsa özyineleme, tekrar doğrusalsa
döngü.** Her özyinelemeli çözüm döngüye çevrilebilir; çeviri, elle bir yığıt tutmayı
gerektirebilir.

#### Özet

- Özyineleme, bir fonksiyonun kendini çağırmasıdır; her çağrının kendi çerçevesi olduğu
  için değişkenler karışmaz.
- Her özyinelemeli tanım taban durumu ve indirgeme adımı içerir; biri eksikse çözüm
  çalışmaz.
- Çağrılar yığıtta birikir, taban durumda açılım durur ve dönüşler ters sırada toplanır.
- Sonsuz özyineleme yığıtı tüketir; imzası, hata iletisinde tekrar eden fonksiyon
  adıdır.
- Özyineleme dallanan yapılarda doğaldır; doğrusal tekrarlarda döngü daha ucuzdur.
- Aynı alt problemin tekrar hesaplanması üstel maliyet üretebilir; not alma bu tekrarı
  ortadan kaldırır.

#### Sonraki Adım

Doğrusal özyinelemelerde çerçeve maliyeti kaçınılmaz görünüyor. Oysa çağrı, gövdenin son
işlemi olduğunda durum değişir: yeni bir çerçeve açmak gerekmez. Sonraki ders bu özel
biçimi — kuyruk özyinelemesini — ve döngüye denkliğini ele alacak.

### Kuyruk Özyinelemesi

Source: https://academia.sh/tr/kurslar/programlama-temelleri/kuyruk-ozyinelemesi

Kuyruk konumundaki çağrı, birikeç parametresiyle dönüşüm, çerçeve yeniden kullanımı ve döngüye denklik.

Önceki ders, doğrusal özyinelemelerde her çağrının bir çerçeve maliyeti getirdiğini
gösterdi. Bu maliyet her zaman kaçınılmaz değildir. Belirli bir yazım biçiminde,
çerçeveyi saklamak için hiçbir neden kalmaz.

Bu dersin konusu o biçim ve onun döngülerle olan tam denkliğidir.

#### Kuyruk Konumu

Bir çağrı, fonksiyonun yaptığı **son işlemse** kuyruk konumundadır: çağrı döndüğünde
yapılacak başka bir iş kalmaz, dönen değer doğrudan dışarı verilir.

Önceki dersteki faktöriyel bu koşulu sağlamaz:

```python
def faktoriyel(n: int) -> int:
    if n <= 1:
        return 1
    return n * faktoriyel(n - 1)      # çağrı döndükten SONRA çarpma var
```

Son satırda özyinelemeli çağrı sonuncu işlem değildir; sonucu `n` ile çarpılacaktır. Bu
nedenle çağıran çerçeve, alt çağrı dönene kadar bekletilmek zorundadır — `n` değerini ve
nereye döneceğini tutar.

Aynı hesabın kuyruk biçimi, henüz yapılmamış işi bir **birikeç parametresine** taşır:

```python
def faktoriyel_kuyruk(n: int, birikec: int = 1) -> int:
    """n! değerini kuyruk özyinelemeli biçimde hesaplar."""
    if n <= 1:
        return birikec                       # taban durumu: birikeci döndür
    return faktoriyel_kuyruk(n - 1, n * birikec)   # son işlem: çağrının kendisi

print(faktoriyel_kuyruk(5))     # 120
print(faktoriyel(5))            # 120  — aynı sonuç
```

Çarpma artık çağrıdan **önce**, argüman hazırlanırken yapılır. Çağrı döndüğünde yapılacak
iş kalmadığı için, çağıran çerçevenin korunmasına gerek yoktur.

#### Çerçeve Neden Gerekmez

Çerçeve iki şey için saklanır: dönüş adresi ve yerel değerler. Kuyruk konumunda alt
çağrının dönüş değeri doğrudan dışarı verileceğinden, çağıranın dönüş adresi ile alt
çağrının dönüş adresi aynıdır. Yerel değerlere de artık gerek yoktur; hepsi argümanlara
aktarılmıştır.

Dolayısıyla yeni bir çerçeve açmak yerine, var olan çerçevenin **yeniden kullanılması**
mümkündür. Bu dönüşüme **kuyruk çağrısı eniyilemesi** denir. Uygulandığında, özyineleme
derinliği ne olursa olsun yığıt büyümez.

#### Dil Desteği Evrensel Değildir

Bu eniyilemenin yapılıp yapılmadığı dile ve gerçekleştirime bağlıdır ve dilin
tanımının parçasıdır — bir hız ayrıntısı değil. Bazı diller eniyilemeyi garanti eder;
o dillerde derin kuyruk özyinelemesi güvenle yazılabilir. Bazıları hiç uygulamaz.

Python uygulamayan diller arasındadır. Kuyruk biçiminde yazılmış bir fonksiyon bile,
derinlik arttığında özyineleme sınırına çarpar:

```python
# faktoriyel_kuyruk(5000)  -> özyineleme sınırı aşıldı hatası
```

Bunun gerekçesi tasarım tercihidir: çerçeveler korunduğunda hata izleme dökümü eksiksiz
kalır ve hata ayıklama kolaylaşır. Ödünleşim, derin özyinelemenin kullanılamamasıdır.

Sonuç şudur: kuyruk biçiminde yazmak, tek başına derinlik güvencesi vermez. Dilin bu
eniyilemeyi yapıp yapmadığı bilinmelidir.

Özyineleme sınırının yükseltilebildiği çalışma zamanlarında bu, çözüm gibi görünür ama
değildir: sınır, gerçek yığıt alanının tükenmesinden önce uyarı vermek için konmuştur.
Yükseltilen sınır, denetimli hatayı denetimsiz bir çökmeye çevirebilir. Doğru çözüm,
derinliği veriyle birlikte büyüyen bir çözümü özyinelemeden çıkarmaktır.

#### Döngüye Denklik

Kuyruk özyinelemesi ile döngü arasındaki ilişki mekaniktir. Birikeç parametreleri döngü
değişkenlerine, indirgeme adımı güncellemelere, taban durumu ise döngü koşuluna karşılık
gelir.

```python
def faktoriyel_dongu(n: int) -> int:
    birikec = 1
    while n > 1:                 # taban durumunun olumsuzu
        birikec = n * birikec    # özyinelemeli çağrının argümanları
        n = n - 1
    return birikec

print(faktoriyel_dongu(5))       # 120
```

Üç sürüm de aynı değeri üretir. Farkları yalnızca yazımda ve yığıt davranışındadır:
döngü sürümü tek çerçeve kullanır ve derinlik sınırı tanımaz.

Karşılıklar tek tek eşlenebilir: birikeç parametresi döngü değişkenine, taban durumunun
olumsuzu döngü koşuluna, özyinelemeli çağrının argümanları ise gövdedeki güncellemelere
karşılık gelir. Bu eşleme, döngü değişmezi kavramının özyinelemedeki karşılığını da
verir — birikecin her çağrıda taşıdığı anlam, döngü değişmezinin tam olarak kendisidir.

Bu denklik, kuyruk çağrısı eniyilemesinin ne yaptığını da açıklar: dönüşümü derleyici
otomatik uygular, elde edilen kod döngü sürümünün kodudur.

#### Kuyruk Olmayan Özyinelemeyi Döngüye Çevirmek

Her özyinelemeli çözüm döngüye çevrilebilir; ancak çağrı kuyruk konumunda değilse
çeviri mekanik olmaz. Çağrıdan sonra yapılacak iş bir yerde saklanmak zorundadır ve
çerçeve kaldırıldığında bu görev programa geçer: **yığıt elle tutulur.**

Ağaç benzeri bir yapıyı gezmek bunun tipik örneğidir. Aşağıdaki çözüm, iç içe listeleri
özyineleme kullanmadan düzleştirir:

```python
def duzlestir(veri: list) -> list[int]:
    """İç içe listeleri tek düzeye indirir; özyineleme kullanmaz."""
    sonuc = []
    yigit = [veri]                       # işlenecek parçalar
    while yigit:
        parca = yigit.pop()
        if isinstance(parca, list):
            yigit.extend(reversed(parca))   # sırayı korumak için ters ekle
        else:
            sonuc.append(parca)
    return sonuc

print(duzlestir([1, [2, [3, 4]], 5]))    # [1, 2, 3, 4, 5]
```

Buradaki `yigit` listesi, özyinelemeli sürümde çağrı yığıtının yaptığı işi yapar:
henüz işlenmemiş parçaları tutar. Kod uzar ve okunabilirlik düşer; karşılığında derinlik
sınırı ortadan kalkar ve bellek kullanımı denetlenebilir hâle gelir.

Seçim ölçütü nettir: derinlik girdiyle sınırsız büyüyorsa ve dil kuyruk çağrısı
eniyilemesi yapmıyorsa, yığıt elle tutulur.

#### Ne Zaman Hangi Biçim

Üç yazım biçiminin seçimi bağlama bağlıdır.

**Doğrudan özyineleme**, problem tanımına en yakın olanıdır ve okunması en kolayıdır.
Derinlik küçük ve sınırlıysa tercih edilir.

**Kuyruk biçimi**, eniyilemeyi garanti eden dillerde derin tekrarlar için doğru
seçimdir. Birikeç parametresi eklendiği için okunabilirlik bir miktar düşer; bu nedenle
birikeçli sürüm genellikle bir yardımcı fonksiyona alınır ve dış arayüz sade tutulur:

```python
def faktoriyel_temiz(n: int) -> int:
    """Dış arayüz sade; birikeç ayrıntısı gizlenir."""
    def yardimci(k: int, birikec: int) -> int:
        if k <= 1:
            return birikec
        return yardimci(k - 1, k * birikec)
    return yardimci(n, 1)

print(faktoriyel_temiz(5))       # 120
```

Bu yazım, kapsam dersindeki iç fonksiyon fikrinin doğrudan uygulamasıdır: yardımcı
fonksiyon dışarıdan görünmez, yalnızca kendi bağlamında anlamlıdır.

**Döngü**, eniyileme garantisi olmayan dillerde ve derinliğin girdiyle büyüdüğü
durumlarda güvenli seçimdir.

Bu üçlü seçim, dillerin tasarım tercihlerini de açıklar. Değiştirilemezliği önceleyen diller,
döngü değişkenini yeniden bağlamak yerine özyinelemeyi asıl tekrar aracı sayar; bu
dillerde kuyruk çağrısı eniyilemesi bir kolaylık değil, zorunluluktur. Buna karşılık
döngüyü asıl araç sayan diller, eniyilemeyi yapmasalar da bir eksiklik doğurmaz — çünkü
aynı işi zaten döngüyle yazmak beklenir. Bir dilin hangi yolu seçtiği, o dilde yazılmış
kodun nasıl göründüğünü belirler; bu ilişki sonraki konunun ana fikridir.

#### Özet

- Bir çağrı, fonksiyonun son işlemiyse kuyruk konumundadır; dönüşten sonra yapılacak iş
  kalmaz.
- Doğrudan özyineleme, henüz yapılmamış işi birikeç parametresine taşıyarak kuyruk
  biçimine çevrilir.
- Kuyruk konumunda çağıran çerçevesi yeniden kullanılabilir; bu dönüşüme kuyruk çağrısı
  eniyilemesi denir.
- Eniyilemenin uygulanması dile bağlıdır ve evrensel değildir; kuyruk biçiminde yazmak
  tek başına derinlik güvencesi vermez.
- Kuyruk özyinelemesi ile döngü arasındaki dönüşüm mekaniktir: birikeçler döngü
  değişkeni, taban durumu döngü koşulu olur.
- Birikeç ayrıntısı, iç yardımcı fonksiyona alınarak dış arayüzden gizlenir.

#### Sonraki Adım

Bu konu, fonksiyonu bir soyutlama aracı olarak kurdu: iş adlandırılır, girdi ve çıktı
sözleşmeye bağlanır, kapsam yalıtılır. Fonksiyon tek soyutlama aracı değildir. Sonraki
konu, programları düzenlemenin üç ayrı yaklaşımını — yapısal, nesneye dayalı ve
fonksiyonel — karşılaştıracak ve aynı problemi her biriyle çözecek.

## Programlama Paradigmalarına Bakış

Yapısal, nesneye dayalı ve fonksiyonel yaklaşımların ayırt edici fikirleri ve dil seçimi ölçütleri.

### Yapısal Programlama

Source: https://academia.sh/tr/kurslar/programlama-temelleri/yapisal-programlama

Sıra, seçim ve yineleme ile program kurma; atlama deyiminin terk edilmesi ve yukarıdan aşağı ayrıştırma.

Bu kurs boyunca kullanılan yapılar — sıralı deyimler, koşullu dallanma, döngüler ve
fonksiyonlar — belirli bir düzenleme anlayışının parçasıdır. Bu anlayışın adı **yapısal
programlamadır** ve bugün doğal görünmesi, tartışmasız kabul edildiği anlamına gelmez;
bir tartışmanın sonucudur.

Bu konu üç düzenleme yaklaşımını karşılaştırır. Her yaklaşım, "bir programı hangi
birimlere böleriz ve bu birimler nasıl konuşur" sorusuna farklı yanıt verir. Üçü de aynı
problemi çözebilir; farkları, büyüyen programlarda ortaya çıkar.

#### Üç Yapı Yeterlidir

Yapısal programlamanın çekirdek iddiası şudur: her hesaplanabilir program, yalnızca üç
denetim yapısı kullanılarak yazılabilir.

- **Sıra:** Deyimlerin arka arkaya yürütülmesi.
- **Seçim:** Koşula göre iki yoldan birinin izlenmesi.
- **Yineleme:** Bir bloğun koşula bağlı olarak tekrarlanması.

Bu üçünün yeterli olduğu, biçimsel olarak kanıtlanmış bir sonuçtur. Pratik anlamı,
dördüncü bir yapıya — özellikle programın herhangi bir noktasına atlayan bir deyime —
ihtiyaç olmadığıdır.

#### Atlama Deyiminin Sorunu

Erken diller, programın istenen satırına doğrudan atlamayı sağlayan bir deyim içeriyordu.
Donanım düzeyinde bu doğaldır: önceki kursta görüldüğü gibi dallanma komutu, program
sayacına yeni bir adres yazmaktan ibarettir.

Sorun, okunabilirlikte ortaya çıkar. Serbest atlamalarla yazılmış bir programda, herhangi
bir satıra hangi yollardan gelinebileceği metne bakılarak anlaşılamaz. Bir değişkenin o
noktadaki değeri hakkında akıl yürütmek için, oraya varan tüm yolların incelenmesi gerekir.
Yol sayısı, atlama sayısıyla birlikte hızla artar.

Yapısal denetim yapıları bu sorunu tanım gereği çözer: her yapının tek bir girişi ve tek
bir çıkışı vardır. Bir bloğun içine yalnızca başından girilir, dışına yalnızca sonundan
çıkılır. Bu kısıt, bir bloğun ne yaptığını bloğun kendisine bakarak anlamayı mümkün kılar.

Erken çıkış deyimleri — döngüden çıkma, fonksiyondan erken dönme — bu kısıtın denetimli
gevşetilmesidir. Serbest atlamadan farkları, hedeflerinin sabit ve yapısal olmasıdır:
çıkış her zaman içinde bulunulan yapının sonuna gider.

#### Yukarıdan Aşağı Ayrıştırma

Yapısal programlamanın ikinci bileşeni, problemin adım adım bölünmesidir. Süreç tepeden
başlar: program, birkaç üst düzey adıma bölünür; her adım, kendi alt adımlarına bölünür;
bölme, her parça doğrudan yazılabilecek kadar küçüldüğünde durur.

Sözde kod dersindeki ayrıştırma tam olarak bu yöntemdir. Ortak problem için:

```
özetle(ölçümler)
├── doğrula(ölçümler)        → boş mu, geçerli mi
├── topla(ölçümler)          → toplam
├── en_büyüğü_bul(ölçümler)  → en büyük
└── ortalama(toplam, sayı)   → ortalama
```

Her düğüm bir yordama karşılık gelir. Üst düzey yordam, alt düzeydekilerin ne yaptığını
bilir; nasıl yaptıklarını bilmez. Bu bilgi saklama, değişikliğin yayılmasını sınırlar:
`en_büyüğü_bul` yordamının içi değiştiğinde, çağıranı etkilenmez.

#### Ortak Problemin Yapısal Çözümü

```python
def dogrula(olcumler: list[int]) -> None:
    """Ölçüm listesinin işlenebilir olduğunu denetler."""
    if not olcumler:
        raise ValueError("ölçüm listesi boş")


def topla(olcumler: list[int]) -> int:
    toplam = 0
    for olcum in olcumler:
        toplam += olcum
    return toplam


def en_buyugu_bul(olcumler: list[int]) -> int:
    en_buyuk = olcumler[0]
    for olcum in olcumler:
        if olcum > en_buyuk:
            en_buyuk = olcum
    return en_buyuk


def ozetle(olcumler: list[int]) -> tuple[float, int]:
    """Ortalama ve en büyük değeri döndürür."""
    dogrula(olcumler)
    return topla(olcumler) / len(olcumler), en_buyugu_bul(olcumler)


print(ozetle([12, 18, 7, 25, 14]))     # (15.2, 25)
```

Çözümün yapısı ayrıştırma ağacıyla birebir örtüşür. Her yordam tek bir iş yapar, kendi
başına sınanabilir ve adı ne yaptığını söyler.

#### Yordamları Gruplamak

Ayrıştırma sürdükçe yordam sayısı artar ve düz bir liste hâline gelir. İkinci bir
düzenleme katmanı gerekir: birbiriyle ilgili yordamlar aynı **modülde** toplanır.

Modül, kapsam dersinde tanımlanan ad alanının pratik karşılığıdır. Ölçüm verisiyle
çalışan yordamlar bir modülde, raporlama yordamları başka bir modülde durur. Modül
sınırı iki iş yapar: adları ayırır ve hangi yordamların birlikte değişeceğini belgeler.

Modülün de bir arayüzü vardır. Dışarıya açılan yordamlar ile yalnızca içeride kullanılan
yardımcılar ayrılır; bu ayrım, önceki kursun bağlama dersindeki iç ve dış görünürlük
kavramının aynısıdır. Dışarıya az sayıda yordam açmak, modülün içini değiştirmeyi
kolaylaştırır.

Bu düzenleme, yapısal programlamanın büyük programlarda uygulanabilir kalmasını sağlayan
şeydir: program, yordamlar hiyerarşisi olarak değil, **modüller arası ilişki** olarak
okunur hâle gelir. Aynı fikrin ölçeklenmiş biçimi — bileşenler ve katmanlar — yazılım
mimarisi müfredatının konusudur.

#### Veri ile Yordamın Ayrı Durması

Bu yaklaşımın ayırt edici özelliği, **verinin ve onu işleyen yordamların ayrı
tanımlanmasıdır.** `olcumler` listesi bir veri yapısıdır; `topla`, `en_buyugu_bul` ise
onun üzerinde çalışan bağımsız yordamlardır. Veri, yordamlara argüman olarak dolaşır.

Küçük programlarda bu düzen açıktır ve fazladan kavram gerektirmez. Program büyüdükçe
iki zorluk belirir.

Birincisi, bir veri yapısının hangi yordamlarla kullanılabileceği kodun hiçbir yerinde
yazılı değildir; ilişki yalnızca geleneğe dayanır. Aynı listeyi yanlış bir yordama
vermek, dilin engellemediği bir hatadır.

İkincisi, veri yapısının kuralları — değişmezleri — dağınık biçimde korunur. "Ölçüm
listesi boş olamaz" kuralı, listeyi kullanan her yordamda yeniden denetlenmek zorundadır;
biri unuttuğunda kural sessizce çiğnenir.

Bu iki zorluk, sonraki dersin çıkış noktasıdır: veri ile onu işleyen davranışı tek bir
birimde toplama fikri.

#### Sınanabilirlik Kazancı

Ayrıştırmanın az konuşulan ama belirleyici bir sonucu, sınanabilirliktir. Tek bir uzun
yordam ancak baştan sona çalıştırılarak denenebilir; bölünmüş yordamların her biri
bağımsız olarak sınanır.

Yukarıdaki çözümde `en_buyugu_bul` yordamı, dosya okumadan, ekrana yazmadan ve başka
hiçbir adıma bağlı olmadan denenebilir: bir liste verilir, dönen değer beklenenle
karşılaştırılır. Kenar durumlar — tek elemanlı liste, tümü eşit değerler — aynı
kolaylıkla denenir.

Bu kazanç, yordamların **saf** olmasıyla artar: girdisi dışında bir şeye bakmayan ve
dışarıda iz bırakmayan yordamın sınanması, hazırlık gerektirmez. Aynı gözlem bu konunun
üçüncü dersinde bir paradigmanın çekirdek fikrine dönüşecektir.

#### Yapısal Programlama Nerede Durur

Yapısal programlama, sonraki paradigmalar tarafından geçersizleştirilmedi; onların
üzerine kurulduğu zemin oldu. Nesneye dayalı bir sınıfın yöntemi de, fonksiyonel bir
dilin fonksiyonu da içeride sıra, seçim ve yinelemeyle yazılır.

Bu nedenle "hangi paradigma" sorusu, denetim yapıları düzeyinde değil, **program hangi
birimlere bölünür** düzeyinde sorulur. Yapısal yanıt, yordamdır.

#### Özet

- Yapısal programlama, her programın sıra, seçim ve yineleme ile yazılabileceğini
  savunur; bu üçü yeterlidir.
- Serbest atlama deyimi, bir noktaya hangi yollardan gelindiğini izlenemez kılar; yapısal
  denetim yapılarının tek girişi ve tek çıkışı vardır.
- Erken çıkış deyimleri, hedefi sabit olduğu için bu kısıtın denetimli gevşetilmesidir.
- Yukarıdan aşağı ayrıştırma, problemi yordamlara böler; üst düzey yordam alt düzeyin ne
  yaptığını bilir, nasıl yaptığını bilmez.
- Veri ile yordamların ayrı durması küçük programlarda sade, büyük programlarda ise
  ilişkinin ve değişmezlerin korunmasını zorlaştıran bir düzendir.
- Bölünmüş yordamlar bağımsız sınanabilir; ilgili yordamlar modüllerde gruplanarak ad
  ayrımı ve değişim sınırı kurulur.

#### Sonraki Adım

Veri ile onu işleyen yordamları aynı birimde toplamak, bu dersin sonunda beliren iki
zorluğa doğrudan yanıt verir. Sonraki ders, nesneye dayalı yaklaşımın temel kavramlarını
tanıtacak ve aynı problemi bu kez bir nesne üzerinden çözecek.

### Nesneye Dayalı Programlamaya Giriş

Source: https://academia.sh/tr/kurslar/programlama-temelleri/nesneye-dayali-programlamaya-giris

Veri ile davranışın birleşmesi, sınıf ve nesne, kapsülleme, değişmezlerin korunması ve arayüz fikri.

Önceki ders iki zorlukla bitti: bir veri yapısının hangi yordamlara ait olduğu kodda
yazılı değildir ve veri yapısının kuralları her kullanan yordamda ayrı ayrı korunmak
zorundadır.

**Nesneye dayalı programlama**, her iki zorluğa aynı hamleyle yanıt verir: veriyi ve onu
işleyen davranışı tek bir birimde toplar. Bu birime **nesne** denir.

#### Sınıf ve Nesne

**Sınıf**, bir nesne türünün tanımıdır: hangi verileri taşıyacağını ve hangi işlemleri
destekleyeceğini söyler. **Nesne**, o tanımdan üretilmiş somut bir örnektir.

Ayrım, tip ile değer arasındaki ayrımın aynısıdır: sınıf kalıp, nesne o kalıptan
üretilmiş bireydir. Tek bir `ÖlçümSerisi` sınıfından, birbirinden bağımsız yüzlerce
ölçüm serisi nesnesi üretilebilir; her birinin kendi verisi vardır, davranışları ortaktır.

```python
class OlcumSerisi:
    """Bir ölçüm dizisini ve üzerindeki işlemleri birlikte taşır."""

    def __init__(self, olcumler: list[int]) -> None:
        if not olcumler:
            raise ValueError("ölçüm listesi boş olamaz")
        self._olcumler = list(olcumler)      # savunma kopyası

    def ekle(self, olcum: int) -> None:
        self._olcumler.append(olcum)

    def ortalama(self) -> float:
        return sum(self._olcumler) / len(self._olcumler)

    def en_buyuk(self) -> int:
        en_buyuk = self._olcumler[0]
        for olcum in self._olcumler:
            if olcum > en_buyuk:
                en_buyuk = olcum
        return en_buyuk

    def sayi(self) -> int:
        return len(self._olcumler)


seri = OlcumSerisi([12, 18, 7, 25, 14])
print(seri.ortalama(), seri.en_buyuk(), seri.sayi())    # 15.2 25 5

seri.ekle(30)
print(seri.ortalama(), seri.en_buyuk())                 # 17.666666666666668 30
```

Yapısal çözümde ayrı ayrı duran `topla`, `en_buyugu_bul` ve veri, burada tek bir birimde
toplanmıştır. Çağrı biçimi de değişmiştir: yordama veri verilmez, nesneye mesaj gönderilir.

#### Kapsülleme ve Değişmezler

Nesnenin verisi, dışarıdan doğrudan erişime kapatılır; erişim yalnızca nesnenin sunduğu
işlemler üzerinden yapılır. Buna **kapsülleme** denir.

Kapsüllemenin gerekçesi gizlilik değil, **değişmezlerin korunmasıdır**. `OlcumSerisi`
sınıfının değişmezi şudur: *ölçüm listesi hiçbir zaman boş değildir.* Bu kural iki yerde
korunur — yaratımda denetlenir ve listeyi boşaltacak bir işlem sunulmaz.

Yapısal çözümde aynı kuralı korumak, listeyi kullanan her yordamda ayrı bir denetim
gerektiriyordu. Burada kural tek bir yerde, verinin sahibinde durur.

Bu, önceki konudaki savunma kopyası tartışmasının da devamıdır: yapıcıda kopya
alınmasaydı, dışarıdaki liste değiştirilerek değişmez çiğnenebilirdi. Dışarı veri
döndüren bir işlem eklenseydi, aynı gerekçeyle kopya döndürmesi gerekirdi.

#### Arayüz ve Gerçekleştirim

Nesnenin dışarıya sunduğu işlemler kümesine **arayüz**, bu işlemlerin nasıl yapıldığına
**gerçekleştirim** denir. Çağıran yalnızca arayüzü bilir.

Ayrımın değeri, gerçekleştirimin serbestçe değiştirilebilmesidir. `OlcumSerisi`, en
büyük değeri her çağrıda baştan hesaplamak yerine ekleme sırasında güncelleyerek
saklayabilir; arayüz aynı kaldığı için çağıran koddan hiçbir satır değişmez.

Bu, yapısal ayrıştırmadaki bilgi saklama fikrinin daha güçlü biçimidir: orada yordamın
içi gizliydi, burada verinin temsili de gizlidir.

#### Nesneler Arası İlişki

Tek bir nesne nadiren yeterlidir; program, birbirine mesaj gönderen nesneler ağı olarak
kurulur. İlişkilerin iki temel biçimi vardır.

**Bileşim**, bir nesnenin başka bir nesneyi parçası olarak taşımasıdır. Ölçüm serisini
saklayan bir rapor nesnesi, seriyi kendi içinde tutar ve ömrünü yönetir.

**İşbirliği**, bir nesnenin başka bir nesneyi tanıyıp ona mesaj göndermesidir; ikisi
bağımsız yaşar.

```python
class Rapor:
    """Bir ölçüm serisinden okunabilir özet üretir."""

    def __init__(self, seri: OlcumSerisi) -> None:
        self._seri = seri                    # işbirliği: seri dışarıda yaratıldı

    def metin(self) -> str:
        return (f"{self._seri.sayi()} ölçüm, "
                f"ortalama {self._seri.ortalama():.1f}, "
                f"en büyük {self._seri.en_buyuk()}")


seri = OlcumSerisi([12, 18, 7, 25, 14])
print(Rapor(seri).metin())      # 5 ölçüm, ortalama 15.2, en büyük 25
```

`Rapor` sınıfı, serinin içini bilmez; yalnızca arayüzünü kullanır. Bu, önceki bölümdeki
arayüz–gerçekleştirim ayrımının nesneler arası karşılığıdır ve tasarımın belirleyici
ölçütünü verir: **bir nesne, başka bir nesnenin iç yapısına ne kadar az bağlıysa
değişiklik o kadar az yayılır.**

Bağımlılığın yönü de bir karardır. Burada rapor seriye bağlıdır, seri rapordan habersizdir;
bu nedenle yeni bir rapor biçimi eklemek seriyi hiç değiştirmez. Ters yönde bir bağımlılık
kurulsaydı, her yeni rapor türü seriyi de değiştirmeyi gerektirirdi.

#### Kalıtım ve Çok Biçimlilik

İki kavram daha bu yaklaşımın parçasıdır ve burada yalnızca tanıtılır.

**Kalıtım**, bir sınıfın başka bir sınıfın tanımını genişletmesidir. Ortak davranış üst
sınıfta bir kez yazılır, alt sınıflar farklılıkları ekler.

**Çok biçimlilik**, aynı mesajın farklı nesne türlerinde farklı davranmasıdır. Çağıran,
karşısındaki nesnenin tam türünü bilmeden aynı işlemi çağırabilir; hangi gerçekleştirimin
çalışacağına nesne karar verir.

Çok biçimliliğin pratik sonucu, koşul zincirlerinin ortadan kalkmasıdır: "türe göre farklı
davran" mantığı, `if` zinciri yerine ayrı sınıflara dağıtılır. Koşullu dallanma dersindeki
eşleme tablosu yaklaşımının nesneye dayalı karşılığı budur.

İkisinin doğru kullanımı — özellikle kalıtımın ne zaman bileşime tercih edileceği —
**Yazılım Tasarımı ve Mimari İlkeler** müfredatının konusudur.

#### Kimlik ve Eşitlik

Nesneler durum taşıdığı için, "aynı" sözcüğü iki anlama gelir. Değişkenler dersindeki
kimlik–eşitlik ayrımı burada tasarım kararına dönüşür.

İki ölçüm serisi aynı değerleri taşıyorsa eşit sayılmalı mıdır, yoksa yalnızca aynı
nesne oldukları durumda mı? Yanıt, nesnenin neyi temsil ettiğine bağlıdır.

**Değer nesneleri** — bir tarih, bir para tutarı, bir koordinat — içerikleriyle
tanımlanır; aynı içeriğe sahip iki örnek eşittir ve genellikle değiştirilemez yapılır.

**Varlık nesneleri** — bir kullanıcı hesabı, bir sipariş — kimlikleriyle tanımlanır. İki
hesabın tüm alanları aynı olsa bile farklı hesaplardır; ayırt edici olan kimliktir.

Bu ayrım yapılmadığında, koleksiyonlarda arama ve karşılaştırma beklenmedik sonuç verir.
Diller genellikle eşitlik davranışının sınıf tarafından tanımlanmasına izin verir; bu
tanım yazılmazsa varsayılan kimlik karşılaştırmasıdır.

Değer ve varlık nesnelerinin ayrımı, alan odaklı tasarımın temel kavramlarındandır ve
**Yazılım Tasarımı ve Mimari İlkeler** müfredatında genişletilir.

#### Ne Zaman Uygun, Ne Zaman Değil

Nesneye dayalı düzenleme, problemin doğal olarak **durumu olan varlıklar** içerdiği
yerlerde güçlüdür: bir hesap, bir bağlantı, bir belge, bir oyun karakteri. Bu varlıkların
kuralları vardır ve kurallar veriyle birlikte durmalıdır.

Buna karşılık, durum taşımayan saf dönüşümler için nesne kurmak gereksiz katman üretir.
İki sayıyı toplayan bir işlem, sınıf gerektirmez.

İkinci eleştiri ölçekle ilgilidir: nesneler durum taşıdığı için, program çok sayıda
değişebilir duruma bölünür. Nesneler birbirine referans tuttukça, bir değişikliğin nereye
kadar yayıldığını izlemek güçleşir. Bu gözlem, sonraki dersin çıkış noktasıdır.

#### Özet

- Nesneye dayalı yaklaşım, veriyi ve onu işleyen davranışı tek bir birimde toplar.
- Sınıf bir tanım, nesne o tanımdan üretilmiş somut örnektir.
- Kapsülleme, veriye erişimi nesnenin işlemleriyle sınırlar; amacı değişmezleri tek bir
  yerde korumaktır.
- Arayüz ile gerçekleştirimin ayrılması, çağıranı etkilemeden iç yapının değiştirilmesine
  izin verir.
- Kalıtım ortak davranışı paylaştırır, çok biçimlilik türe göre davranışı koşul zinciri
  olmadan seçtirir.
- Yaklaşım, durumu olan varlıklarda güçlüdür; saf dönüşümlerde gereksiz katman üretir ve
  ölçekte yayılan değişebilir durum bir maliyettir.

#### Sonraki Adım

Değişebilir durumun izlenmesi güçleşiyorsa, akla gelen karşı fikir açıktır: durumu hiç
değiştirmemek. Sonraki ders, hesaplamayı değer dönüşümü olarak ele alan fonksiyonel
yaklaşımı tanıtacak ve aynı problemi üçüncü kez, bu kez hiçbir değeri değiştirmeden
çözecek.

### Fonksiyonel Programlamaya Giriş

Source: https://academia.sh/tr/kurslar/programlama-temelleri/fonksiyonel-programlamaya-giris

Saf fonksiyonlar, değiştirilemezlik, fonksiyonun değer olarak kullanımı, dönüşüm zincirleri ve yan etki sınırı.

Önceki ders, nesnelerin durum taşıdığını ve programın çok sayıda değişebilir duruma
bölündüğünü belirtti. Bir değişikliğin nereye kadar yayıldığını izlemek, program
büyüdükçe güçleşir.

**Fonksiyonel programlama** bu soruna kökten bir yanıt verir: değerleri hiç değiştirme.
Hesaplama, var olan bir şeyi güncellemek değil, girdiden yeni bir değer **üretmektir**.

#### Saf Fonksiyon

Bir fonksiyon iki koşulu sağlıyorsa **saftır**:

1. Aynı girdi için her zaman aynı sonucu verir.
2. Gözlenebilir hiçbir yan etkisi yoktur — argümanını değiştirmez, dışarıdaki durumu
   güncellemez, ekrana yazmaz.

```python
def ortalama(olcumler: list[int]) -> float:      # saf
    return sum(olcumler) / len(olcumler)

toplam_sayac = 0

def kirli_ortalama(olcumler: list[int]) -> float:   # saf değil
    global toplam_sayac
    toplam_sayac += 1                                # dış durum değişti
    olcumler.sort()                                  # argüman değişti
    return sum(olcumler) / len(olcumler)
```

Saflığın dört pratik sonucu vardır. Fonksiyon, bağlamından bağımsız olarak sınanabilir:
girdi verilir, çıktı denetlenir. Sonucu saklanabilir — özyineleme dersindeki not alma
tekniği yalnızca saf fonksiyonlarda geçerlidir. Çağrıları birbirinden bağımsız olduğu
için farklı iş parçacıklarında paralel çalıştırılabilir. Ve okunurken yalnızca kendisine
bakmak yeterlidir; uzaktaki bir durumun ne olduğunu bilmek gerekmez.

#### Değiştirilemezlik

Saflığın önkoşulu, değerlerin değiştirilmemesidir. Bir listeye eleman eklemek yerine,
eski elemanlar artı yenisini içeren **yeni bir liste** üretilir.

```python
olcumler = [12, 18, 7]

genisletilmis = olcumler + [25]          # yeni liste üretildi
print(olcumler, genisletilmis)           # [12, 18, 7] [12, 18, 7, 25]

sirali = sorted(olcumler)                # yeni liste
print(olcumler, sirali)                  # [12, 18, 7] [7, 12, 18]
```

İlk bakışta bu, kopya maliyeti demektir. Fonksiyonel dillerde bu maliyet, **kalıcı veri
yapılarıyla** azaltılır: yeni sürüm, eski sürümün değişmeyen parçalarını paylaşır; yalnızca
değişen yol kopyalanır. Veri yapıları kursunda bu yapılar ele alınır.

Değiştirilemezliğin kazancı, değişkenler dersindeki paylaşım sorununun ortadan
kalkmasıdır. Değiştirilemez bir değeri paylaşmak güvenlidir; kimse onu başkasının
altından değiştiremez. Aynı gerekçe, savunma kopyası ihtiyacını da ortadan kaldırır.

#### Fonksiyonlar Değerdir

Fonksiyonel yaklaşımın ikinci temel fikri, fonksiyonun da bir değer olmasıdır:
değişkene bağlanabilir, argüman olarak geçirilebilir, sonuç olarak döndürülebilir.
Kapsam dersindeki kapanış örneği bunun ilk uygulamasıydı.

Fonksiyon alan veya döndüren fonksiyonlara **yüksek mertebeden fonksiyon** denir. Üçü
neredeyse her dilde bulunur:

- **Eşleme (map):** Her elemanı bir fonksiyondan geçirir, aynı uzunlukta yeni bir dizi
  üretir.
- **Süzme (filter):** Koşulu sağlayan elemanları seçer.
- **İndirgeme (reduce):** Diziyi, bir birleştirme fonksiyonuyla tek bir değere indirir.

```python
from functools import reduce

ham = [12, -1, 18, -1, 25, 14]

gecerli = list(filter(lambda o: o >= 0, ham))          # süzme
print(gecerli)                                          # [12, 18, 25, 14]

metrik = list(map(lambda o: o / 100, gecerli))          # eşleme
print(metrik)                                           # [0.12, 0.18, 0.25, 0.14]

toplam = reduce(lambda a, b: a + b, gecerli, 0)         # indirgeme
en_buyuk = reduce(lambda a, b: a if a > b else b, gecerli)

print(toplam, toplam / len(gecerli), en_buyuk)          # 69 17.25 25
```

Üç işlem, birikeç kalıbının adlandırılmış hâlleridir. Döngü sürümünde "ne yapıldığı"
gövdeye gömülüyken, burada işlemin adı doğrudan görünür: süzülüyor mu, dönüştürülüyor mu,
toplanıyor mu.

İndirgemenin başlangıç değeri, döngüler dersindeki etkisiz eleman tartışmasının aynısıdır;
boş dizide başlangıç değeri verilmezse indirgeme tanımsızdır.

#### Bileşim

Küçük ve saf fonksiyonlar, çıktısı bir sonrakinin girdisi olacak biçimde zincirlenir. Bu
zincire **bileşim** denir ve fonksiyonel programların temel düzenleme biçimidir.

```python
def gecerli_olanlar(olcumler: list[int]) -> list[int]:
    return [o for o in olcumler if o >= 0]

def ortalama(olcumler: list[int]) -> float:
    return sum(olcumler) / len(olcumler)

def ozet(ham: list[int]) -> tuple[float, int]:
    """Ham veriden ortalama ve en büyük değeri üretir; hiçbir şeyi değiştirmez."""
    gecerli = gecerli_olanlar(ham)
    return ortalama(gecerli), max(gecerli)

ham = [12, -1, 18, -1, 25, 14]
print(ozet(ham))                 # (17.25, 25)
print(ham)                       # [12, -1, 18, -1, 25, 14]  — girdi değişmedi
```

Nesneye dayalı çözümde veri nesnenin içinde durup mesajlarla değişiyordu; burada veri
fonksiyonlar arasında akıyor ve her adım yeni bir değer üretiyor.

#### Tembel Değerlendirme

Dönüşüm zincirleri, her adımda ara bir koleksiyon üretme eğilimindedir: süzme yeni bir
liste, eşleme bir yenisini daha. Girdi büyükse bu ara sonuçlar bellekte gereksiz yer
kaplar.

Döngü denetimi dersinde tanıtılan tembel yineleme bu sorunu çözer: değerler istendikçe
üretilir, zincirin her adımı tek bir eleman üzerinden akar. Ara liste hiç oluşmaz.

Tembelliğin ikinci sonucu, **sonsuz dizilerin** tanımlanabilmesidir. Sonu olmayan bir
diziden ilk beş eleman alınabilir; alınmayanlar hesaplanmadığı için tanım sonsuz olsa da
hesap sonludur. Bazı fonksiyonel diller tembelliği varsayılan davranış yapar; çoğu dilde
ise ayrı bir yapı olarak sunulur.

Bedeli, hesabın **ne zaman** yapıldığının belirsizleşmesidir: zincir kurulduğunda değil,
sonuç istendiğinde çalışır. Yan etkili kodla birleştiğinde bu, sırası tahmin edilemeyen
davranışa yol açar — tembelliğin saf fonksiyonlarla birlikte anılmasının nedeni budur.

#### Yan Etkiler Nereye Gider

Hiçbir yan etkisi olmayan program işe yaramaz: sonuç bir yere yazılmalı, girdi bir yerden
okunmalıdır. Fonksiyonel yaklaşım yan etkileri yok saymaz, **sınıra taşır**.

Yaygın düzen şudur: programın çekirdeği saf fonksiyonlardan oluşur, dış kabuk girdi–çıktı
işlerini yapar. Kabuk veriyi okur, çekirdeğe verir, dönen sonucu yazar. Böylece iş
mantığının tamamı sınanabilir kalır; sınanması güç olan kısım incelir.

Bu düzen, yalnızca fonksiyonel dillerde değil her dilde uygulanabilir. Kurs boyunca
yazılan hesaplama fonksiyonlarının hiçbirinin ekrana yazmaması, bu ayrımın bilinçli
uygulanmasıydı.

#### Sınırları

Fonksiyonel yaklaşımın da bedelleri vardır. Doğası gereği durum taşıyan problemler —
bir oyunun anlık durumu, bir bağlantının açık olup olmaması — durumu değere çevirmeyi ve
her adımda yeni bir sürüm üretmeyi gerektirir; bu, kimi zaman doğal olmayan bir modelleme
yaratır.

Başarım da her zaman kendiliğinden gelmez: kalıcı yapıların paylaşımı iyi olsa da,
değiştirilebilir bir dizinin yerinde güncellenmesi genellikle daha ucuzdur.

Bu nedenle çoğu dil tek bir paradigmaya bağlı kalmaz. Saf çekirdek ile durumlu kenar,
nesnelerle fonksiyonların bir arada kullanılması yaygın pratiktir.

#### Özet

- Saf fonksiyon aynı girdi için aynı sonucu verir ve yan etkisi yoktur; sınanabilirlik,
  not alma ve paralellik bu iki koşuldan doğar.
- Değiştirilemezlik, değerleri güncellemek yerine yeni değer üretmektir; paylaşımı güvenli
  kılar ve savunma kopyası ihtiyacını kaldırır.
- Fonksiyonlar değerdir; eşleme, süzme ve indirgeme birikeç kalıbının adlandırılmış
  biçimleridir.
- Bileşim, küçük saf fonksiyonların zincirlenmesidir ve fonksiyonel programın temel
  düzenleme biçimidir.
- Yan etkiler yok sayılmaz, programın sınırına taşınır: saf çekirdek, durumlu kabuk.
- Doğası gereği durumlu problemlerde ve yerinde güncellemenin ucuz olduğu yerlerde
  yaklaşımın maliyeti vardır.

#### Sonraki Adım

Üç paradigma da aynı problemi çözdü ve üçü de farklı dillerde farklı ağırlıkta
destekleniyor. Kursun son dersi, bir dilin hangi eksenlerde ayrıştığını ve öğrenilecek
dilin nasıl seçileceğini ele alacak; ardından bu kursta kurulan kavramların hangi kursla
sürdüğünü söyleyecek.

### Bir Dil Seçmek

Source: https://academia.sh/tr/kurslar/programlama-temelleri/bir-dil-secmek

Dilleri ayrıştıran tasarım eksenleri, ilk dil seçimi ölçütleri ve aktarılabilir bilgi.

Bu kurs, kavramları belirli bir dile bağlı kalmadan kurdu; örnekler tek bir dilde
yazılsa da anlatılanların hiçbiri o dile özgü değildi. Kursu kapatan soru şudur: diller
birbirinden neyle ayrılır ve hangisiyle başlanmalıdır?

Sorunun "en iyi dil hangisidir" biçiminde bir yanıtı yoktur. Diller farklı ödünleşimleri
seçmiş tasarımlardır; bir eksende kazandıkları, başka bir eksende ödedikleriyle gelir.

#### Diller Neden Ayrışır

Her dil, belirli bir sorun kümesini kolaylaştırmak için tasarlanır ve bu tercih, başka
bir şeyi zorlaştırır. Donanıma yakın denetim isteyen bir dil, bellek yönetimini
programcıya bırakır; güvenliği önceleyen bir dil, aynı denetimi kısıtlar. Hızlı yazım
sunan bir dil, hataların bir bölümünü çalışma anına erteler.

Bu nedenle diller karşılaştırılırken sorulacak soru "hangisi daha iyi" değil, **"hangi
ödünleşimi seçmiş"** olmalıdır.

#### Ayırt Edici Eksenler

Kurs boyunca değinilen ayrımlar, dilleri konumlandıran eksenlerdir:

| Eksen | Uçlar | Kursta geçtiği yer |
|---|---|---|
| Tip denetimi zamanı | Statik ↔ dinamik | Temel veri tipleri |
| Tip katılığı | Örtük dönüşüme kapalı ↔ açık | Tip dönüşümü |
| Bellek yönetimi | Elle ↔ otomatik toplama | Bellek düzeni (önceki kurs) |
| Yürütme modeli | Derleme ↔ yorumlama ↔ sanal makine | Derleme zinciri (önceki kurs) |
| Değişkenlik | Değiştirilemez varsayılan ↔ değiştirilebilir varsayılan | Fonksiyonel yaklaşım |
| Paradigma ağırlığı | Nesne merkezli ↔ fonksiyon merkezli ↔ çok paradigmalı | Bu konu |
| Eşzamanlılık modeli | İş parçacığı ↔ olay döngüsü ↔ mesajlaşma | İşletim sistemi kavramları |

Bir dili öğrenmek, bu eksenlerdeki konumunu öğrenmektir. Sözdizim birkaç günde
alışılır; asıl öğrenilen, dilin hangi hataları önlediği ve hangilerini programcıya
bıraktığıdır.

Sekizinci bir eksen teknik değildir ama belirleyicidir: **ekosistem**. Bir dilin
kütüphaneleri, araçları ve topluluk birikimi, o dille çözülebilecek problemlerin
sınırını çizer. Dil tasarımı iyi olsa da hedef alan için kütüphane yoksa, iş dil
seçiminden önce başlar.

#### İlk Dil Seçimi

Öğrenmeye başlarken seçim, dilin gücünden çok **öğrenme deneyimine** bakılarak yapılır.
Dört ölçüt işe yarar:

**Geri bildirim hızı.** Yazılan kodun sonucunun kısa sürede görülmesi, öğrenmeyi
hızlandırır. Uzun derleme ve kurulum adımları, başlangıç için ek yüktür.

**Hata iletilerinin niteliği.** İyi bir ileti sorunun ne olduğunu, nerede olduğunu ve ne
beklendiğini söyler. Başlangıç düzeyinde bu, öğreticiden daha çok şey öğretir.

**Kavramların görünürlüğü.** Bazı diller bu kursun kavramlarını açıkça gösterir: tip
bildirimi, kapsam kuralları, bellek davranışı. Bazıları bunları gizler; kolaylık sağlar
ama kavram öğrenmeyi geciktirir.

**Uygulanabilirlik.** Öğrenilen dille kısa sürede işe yarar bir şey üretilebilmesi,
sürekliliği korur.

Bu ölçütler tek bir dile işaret etmez ve etmemelidir. Önemli olan, seçilen dille yeterince
uzun kalıp kavramları oturtmaktır; sık dil değiştirmek, her seferinde sözdizim öğrenip
kavramlara hiç ulaşmamakla sonuçlanır.

#### Aktarılabilen ve Aktarılamayan

Yeni bir dile geçerken bilginin ne kadarının taşındığı, ne öğrenildiğine bağlıdır.

**Aktarılabilenler:** Bu kursun tamamı. Değişken ve bağlama, tip kavramı, denetim
akışı, döngü değişmezi, fonksiyon sözleşmesi, kapsam kuralları, özyineleme, argüman
aktarım modelleri, paradigmaların ayırt edici fikirleri. Bunlar dile değil, programlamaya
aittir.

**Aktarılamayanlar:** Sözdizim ayrıntıları, standart kütüphane adları, araç zinciri
komutları, dile özgü deyimler. Bunlar her dilde yeniden öğrenilir ve öğrenilmesi kısa
sürer.

Oran, deneyimle birlikte değişir: ilk dilde neredeyse her şey yeni görünür; üçüncü dilde
öğrenilecek olan yalnızca eksenlerdeki konum ve sözdizimdir. Bu nedenle ilk dilde
harcanan zaman, sonraki dillerin tamamına yayılan bir yatırımdır.

İkinci dili seçerken yararlı bir ölçüt, **birinciden bir eksende belirgin biçimde
ayrılmasıdır**. Dinamik tipli bir dilden sonra statik tipli bir dil öğrenmek, tip
kavramını iki taraftan birden görmeyi sağlar; otomatik bellek yönetimli bir dilden
sonra elle yönetilen bir dil, bellek modelini görünür kılar.

#### Problem Alanı Seçimi Daraltır

Bir proje için dil seçimi ise farklı bir sorudur ve genellikle daha az serbestlik içerir.
Belirleyiciler şunlardır: hedef platformun desteklediği diller, alanın gerektirdiği
kütüphaneler, ekibin var olan bilgisi ve sürdürülebilirlik.

Son madde çoğu zaman en ağır basanıdır. Kod, yazıldığından çok daha uzun süre okunur ve
bakımı yapılır; ekibin okuyabildiği bir dil, teknik olarak üstün ama kimsenin bilmediği
bir dilden daha iyi bir seçimdir.

#### Diller Değişir

Bir dil, seçildiği anda sabitlenmiş bir araç değildir; sürümlerle evrilir. Yeni yapılar
eklenir, hatalı kararlar düzeltilir, bazı özellikler kullanımdan kaldırılır.

Bu evrim, dilleri ayıran bir eksen daha üretir: **geriye dönük uyumluluk politikası.**
Kimi diller eski kodun çalışmayı sürdürmesini öncelikli sayar ve bu yüzden hatalı
kararlarla birlikte yaşar. Kimileri temiz tasarım için kırıcı değişikliği göze alır ve
büyük kod tabanlarına geçiş maliyeti yükler.

Pratik sonucu, öğrenilen bilginin bir bölümünün sürüme bağlı olmasıdır. Kavramlar
kalıcıdır; bir yapının hangi sürümde kullanılabilir olduğu ise değişkendir. Bu nedenle
kaynaklarda ve belgelerde hangi sürümün anlatıldığı, kodun hangi sürümü hedeflediği
açıkça belirtilir.

Aynı ilke bağımlılıklar için de geçerlidir: kütüphaneler de sürümlenir ve uyumluluk
kuralları aynı biçimde işler. Sürüm yönetimi, yazılım geliştirme pratiği müfredatının
konusudur.

#### Bu Kursun Toparlanması

Kurs, program kavramından başlayıp üç düzenleme yaklaşımına ulaştı. Sırayla: bir program
girdi–işlem–çıktı dönüşümüdür; değerler adlara bağlanır ve tipleri, üzerlerinde hangi
işlemlerin tanımlı olduğunu söyler; akış koşullar ve döngülerle yönlendirilir ve her
döngünün bir sonlanma gerekçesi ile bir değişmezi vardır; fonksiyonlar işi adlandırıp
sözleşmeye bağlar, kapsamı yalıtır ve özyinelemeyi mümkün kılar; paradigmalar ise bu
parçaların hangi birimlerde toplanacağını belirler.

Aynı problem — bir ölçüm dizisinin özeti — kurs boyunca dört kez çözüldü: elle döngüyle,
yordamlara bölünmüş biçimde, nesne üzerinden ve saf fonksiyonlarla. Dördü de doğru
çalıştı. Aralarındaki fark hızda değil, **değişikliğe nasıl karşılık verdiklerindedir** —
ve bu, program büyüdükçe belirleyici olan ölçüttür.

#### Özet

- Diller, farklı ödünleşimleri seçmiş tasarımlardır; karşılaştırma "hangisi daha iyi"
  değil "hangi ödünleşimi seçmiş" sorusuyla yapılır.
- Tip denetimi zamanı, tip katılığı, bellek yönetimi, yürütme modeli, değişkenlik,
  paradigma ağırlığı ve eşzamanlılık modeli başlıca ayırt edici eksenlerdir; ekosistem
  ise teknik olmayan ama belirleyici bir etkendir.
- İlk dil seçiminde geri bildirim hızı, hata iletilerinin niteliği, kavramların
  görünürlüğü ve uygulanabilirlik ölçüt alınır.
- Kavramlar dilden dile aktarılır; sözdizim, kütüphane adları ve araç komutları
  aktarılmaz.
- İkinci dil, birinciden bir eksende belirgin ayrılan bir dil olduğunda o ekseni
  görünür kılar.
- Proje için dil seçiminde platform, kütüphane, ekip bilgisi ve sürdürülebilirlik
  belirleyicidir.

#### Sonraki Adım

Bu kursta veriler hep hazır koleksiyonlarda tutuldu: liste verildi, üzerinde gezildi.
Peki bir koleksiyon nasıl saklanır, arama neden bazı yapılarda hızlı bazılarında yavaştır
ve hangi yapı hangi işlem için uygundur? Bundan sonraki kurs — **Veri Yapıları** — bu
soruları ele alacak ve önceki kurstaki bellek modeliyle bu kurstaki soyutlama araçlarını
bir araya getirecek.

