Ders 13 / 15
Yineleyici Trait'i
Bir kaynağın üzerinde dolaşmanın tek bir sözleşmeye — sırada bir sonraki değeri isteyen tek bir çağrıya — indirgendiği ve bu sözleşmenin yalnızca istendiğinde ilerlediği ölçülüyor: kurulan bir zincir hiç tüketilmezse gövdesi hiç koşmuyor.
Yayın Güncelleme
Bu derste
- Bir kaynak üzerinde dolaşmanın tek bir sözleşmeye — sırada bir sonraki değeri isteyen çağrıya — indirgendiğini kendi yazdığı bir sözleşme gerçekleştirimiyle gösterir
- Kurulan ama tüketilmeyen bir zincirin gövdesinin hiç koşmadığını, aynı kaynağın tüketilen bir eşdeğeriyle karşılaştırarak ölçer
- Zincir kurmanın kendisiyle zincirin ilerlemesini birbirinden ayırt eder
İçindekiler
Önceki ders tek bir kapanışın ne zaman koştuğunu ölçtü: saklanan bir gövde, çağrılmadığı
sürece hiç koşmuyordu. Bu ders aynı soruyu tek bir kapanıştan bir kaynağın tamamına
taşıyor. Bir dizi, bir aralık, bir dizgi — hepsi üzerinde “dolaşmak” farklı bir işlem gibi
görünür, ama Rust bunların hepsini tek bir sözleşmeye indirger: sırada bir sonraki değeri
isteyen tek bir çağrı. Bu ders o sözleşmenin kendisini yazıp, dolaşmanın gerçekte bu tek
çağrının tekrarı olduğunu ve bir kaynağın üzerinde kurulmuş bir zincirin bu çağrı hiç
gelmeden koşum defterine hiç girmediğini ölçüyor. Ölçülen, kaynağın türü ya da içeriği
değil; sözleşmenin yönteminin ne zaman ve kaç kez işletildiğidir. Bu, kapanışlar
konusundaki ölçümün doğal bir uzantısı: orada tek bir gövdenin saklanma biçimi
belirleyiciydi, burada birçok kapanışın birbirine bağlandığı bir sözleşmenin kendisi
belirleyici. Sözleşme değişmeden kalırken, onu kim gerçekleştirdiği — bir dizi, bir
aralık, ya da bu derste elle yazılan Sayici — değişebilir; ölçülen sayı bu seçimden
bağımsızdır.
Dolaşmanın Tek Bir Sözleşmeye İndirgenmesi
Bir for döngüsü, bir dönüşüm zinciri, bir toplama çağrısı — kaynak metinde birbirinden
çok farklı görünen bu yazımların hepsi, altta aynı tek yönteme iniyor: sırada bir sonraki
değeri iste, yoksa dur. Bu yöntemin adı ve imzası tek bir sözleşmede toplanıyor; bir tip bu
sözleşmeyi gerçekleştirdiğinde, üzerinde dolaşılabilir hale geliyor. Sözleşmenin kendisi
ne kaynağın nasıl saklandığını, ne de kaç elemanı olduğunu söylüyor — yalnızca “bir
sonrakini ver” sorusuna nasıl yanıt vereceğini.
Standart kitaplığın kendi kaynakları (bir büyüyen dizi, bir aralık) bu sözleşmeyi zaten
gerçekleştiriyor, ama gerçekleştirimin içine bir sayaç yerleştirilemez — sözleşmenin
yöntemi kaç kez çağrıldı diye doğrudan sorulamaz. Bu yüzden aşağıdaki koşum, sözleşmeyi
kendi yazdığı küçük bir tipte gerçekleştiriyor: Sayici, tek bir sayaç tutan ve isteneni
bir bir üreten bir kaynak. Sözleşmenin kendisi standart kitaplığınkiyle birebir aynı;
farklı olan yalnızca, yöntemin her çağrıldığında bunu deftere yazması.
YN5. — “üç elemanlı bir kaynak üzerinde dolaşmanın, kaynağın kendi sözleşme gerçekleştiriminin tek bir yöntemine dört kez uğradığı” iddiası, aşağıdaki koşumla doğrulanıyor.
// sayici_dolasim.rs — dolasmanin tek bir sonraki-deger cagrisina indirgendigini olcuyor
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;
type Defter = Rc<RefCell<BTreeMap<String, u32>>>;
fn say(defter: &Defter, ad: &str) {
*defter.borrow_mut().entry(ad.to_string()).or_insert(0) += 1;
}
fn kere(defter: &Defter, ad: &str) -> u32 {
*defter.borrow().get(ad).unwrap_or(&0)
}
struct Sayici {
su_an: u32,
sinir: u32,
defter: Defter,
}
impl Iterator for Sayici {
type Item = u32;
fn next(&mut self) -> Option<u32> {
say(&self.defter, "sonraki-deger-cagrisi");
if self.su_an < self.sinir {
self.su_an += 1;
Some(self.su_an)
} else {
None
}
}
}
fn main() {
let defter: Defter = Rc::new(RefCell::new(BTreeMap::new()));
let sayici = Sayici { su_an: 0, sinir: 3, defter: Rc::clone(&defter) };
let mut toplam = 0;
for deger in sayici {
toplam += deger;
}
println!("toplam: {}", toplam);
println!("sonraki deger cagrisi sayisi: {}", kere(&defter, "sonraki-deger-cagrisi"));
}
toplam: 6 sonraki deger cagrisi sayisi: 4
Sayici, sözleşmenin tek yöntemini gerçekleştiriyor: bir sonraki değer varsa onu veriyor
ve kendi durumunu bir ilerletiyor, yoksa boş bir yanıt döndürüyor. Kaynak metinde for
döngüsü tek bir yazım gibi görünse de, koşumda bu yöntem üç elemanlı Sayici üzerinde dört
kez çağrılıyor: üç kez bir değerle, bir kez de “artık yok” yanıtıyla. Döngü bu dördüncü
çağrıyı görene kadar durmuyor — kendisi eleman saymıyor, yalnızca yanıtı boş olana kadar
sormaya devam ediyor. Toplamın altı çıkması (1 + 2 + 3) ile çağrı sayısının dört olması
aynı koşumun iki farklı yüzü: biri elemanların değerini, öbürü sözleşmenin kaç kez
işletildiğini gösteriyor. Bu ölçü, M08/K04’ün ölçtüğü “adımı hangi iş parçacığı
koşturuyor” sorusuyla karışmaz: burada tek bir iş parçacığı var, soru yalnızca sözleşmenin
kaç kez işletildiğidir.
Dördüncü çağrı, kaynak metinde hiçbir yerde açıkça yazılmıyor — for döngüsünün kendisi bu
çağrıyı üretiyor, “artık değer yok” yanıtını görene kadar sormaya devam ederek. Bu, dolaşan
tarafın (döngünün) kaynağın kaç eleman taşıdığını önceden bilmesi gerekmediği anlamına
geliyor: Sayicinin sınırı üç olarak kuruldu, ama döngü bu sınırı hiç okumadı, yalnızca
yanıtları izledi. Sözleşme, kaynağın büyüklüğünü kaynağın kendisine saklı tutuyor.
Sözleşmenin tek yöntemi, kaynağın tamamının dolaşılmasını da zorunlu kılmıyor — yalnızca
kaç kez istenirse o kadar ilerliyor. Aynı Sayici, sınırı beş olacak biçimde kurulup
yalnızca iki kez sorguluyor.
YN6. — “beş elemanlık bir kaynağın yalnızca ilk iki değeri istendiğinde, sözleşmenin yönteminin beş değil iki kez çağrıldığı” iddiası, aşağıdaki koşumla doğrulanıyor.
// kismi_tuketim.rs — bir yineleyici sonuna kadar tuketilmeden de birakilabiliyor
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;
type Defter = Rc<RefCell<BTreeMap<String, u32>>>;
fn say(defter: &Defter, ad: &str) {
*defter.borrow_mut().entry(ad.to_string()).or_insert(0) += 1;
}
fn kere(defter: &Defter, ad: &str) -> u32 {
*defter.borrow().get(ad).unwrap_or(&0)
}
struct Sayici {
su_an: u32,
sinir: u32,
defter: Defter,
}
impl Iterator for Sayici {
type Item = u32;
fn next(&mut self) -> Option<u32> {
say(&self.defter, "kismi-sonraki-deger-cagrisi");
if self.su_an < self.sinir {
self.su_an += 1;
Some(self.su_an)
} else {
None
}
}
}
fn main() {
let defter: Defter = Rc::new(RefCell::new(BTreeMap::new()));
let mut sayici = Sayici { su_an: 0, sinir: 5, defter: Rc::clone(&defter) };
let ilk = sayici.next();
let ikinci = sayici.next();
println!("ilk iki deger: {:?}, {:?}", ilk, ikinci);
println!("sinir bes iken cagri sayisi: {}", kere(&defter, "kismi-sonraki-deger-cagrisi"));
}
ilk iki deger: Some(1), Some(2) sinir bes iken cagri sayisi: 2
sayici beş değere kadar ilerleyebilecek biçimde kuruldu, ama yalnızca iki kez açıkça
sorgulandı ve kalan üç değere hiç uğranmadı — sayici burada kapsamının sonunda sessizce
bırakılıyor. Sözleşmenin yöntemi, kaynağın “bitmesini” değil, “istenmesini” bekliyor:
kaynak beş eleman taşısa da, taşıdığı sayı çağrı sayısını belirlemiyor, yalnızca üst sınırı
belirliyor. Bu gözlem sıradaki dersin eksenini önceden kuruyor: bir kaynağın uzunluğu ile
üzerinde çalışan gövdenin kaç kez koştuğu aynı sayı değildir.
Bu koşumda yöntem elle, iki ayrı satırda çağrıldı; bir önceki koşumda ise aynı yöntemi bir
döngü çağırıyordu. İkisi arasındaki fark yalnızca çağıranın kimliğinde: birinde çağıran
kod bizim yazdığımız main, öbüründe çağıran kod for döngüsünün kendisi. Sözleşmenin
yöntemi her iki durumda da aynı imzayı taşıyor ve aynı biçimde yanıt veriyor — kimin
çağırdığı, sözleşmenin davranışını değiştirmiyor, yalnızca çağrının kaynak metinde nerede
göründüğünü değiştiriyor. Bu, sözleşmenin “tek” olmasının tam anlamı: dolaşımın hangi
yazımla ifade edildiğinden bağımsız, altta hep aynı yönteme iniyor.
Sözleşmenin bu tekliği, bir dolaşım biçiminin başka bir dolaşım biçimine dönüştürülmesini
de basitleştiriyor: bir dönüşüm zinciri ya da bir arama çağrısı yazıldığında, bunların
hepsi aynı sözleşmenin üstüne kuruluyor, ayrı bir dolaşım kuralı öğrenmeye gerek kalmıyor.
Bu üst yöntemlerin çoğu, kursun açılış dersinde ölçülen türden varsayılan gövdelerdir —
kendileri next çağrısını içermez, nexti tekrar tekrar çağıran hazır bir gövdeyi
devralırlar; orada ölçülen, bir varsayılan gövdenin onu yeniden yazan bir tip için hiç
koşmadığıydı, burada ölçülen ise aynı mekanizmanın dolaşım sözleşmesindeki somut bir
örneği. Ne var ki bu sözleşmenin var olması, dolaşmanın kaynak kodda yazılmasıyla koşumda
gerçekleşmesinin aynı an olduğu anlamına gelmiyor — bu ayrım sıradaki ölçünün konusu.
Kurulmuş Ama Hiç Tüketilmemiş Zincir
Bir kaynak üzerinde bir dönüşüm tanımlamak, o dönüşümü çalıştırmakla aynı şey değildir. Bir
önceki ölçüm sözleşmenin yöntemini elle çağırdı; bu ölçüm aynı sözleşmenin üstüne kurulan,
kaynak metinde çok daha sık görülen bir yazımı — bir dönüşüm zinciri — ele alıyor. Bir
dönüşüm zinciri yazmak (.map(...) gibi), kaynağı ve bir kapanışı bir araya getirip yeni
bir kaynak üreten bir çağrıdır; ürettiği şey de kendisi sözleşmeyi gerçekleştiren bir
değerdir. Kursun ölçüm çekirdeğindeki bir satır, dört elemanlı bir kaynağı dönüştüren bir
zincir kuruyor ama bu zinciri hiçbir yerde tüketmiyor — sonucu ne bir döngüye veriyor, ne
bir listeye topluyor, ne de bir toplama çağrısına yolluyor. Zincirin bu haliyle koşup
koşmadığı, tembel değerlendirme kavramının bir uygulamasıdır; bu kavram Programlama
Temelleri kursunda kuruldu ve burada yeniden anlatılmıyor, yalnızca kaç kez koştuğu
sayılıyor.
YN7. — “aynı dört elemanlı kaynağın bir kez hiç tüketilmeden, bir kez toplama çağrısıyla tüketilerek kurulduğunda, tüketilmeyen gövdenin sıfır, tüketilenin dört kez koştuğu” iddiası, aşağıdaki koşumla doğrulanıyor.
// tuketilmeyen_ve_tuketilen.rs — ayni kaynak bir kez tuketilmeden, bir kez tuketilerek kuruluyor
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;
type Defter = Rc<RefCell<BTreeMap<String, u32>>>;
fn say(defter: &Defter, ad: &str) {
*defter.borrow_mut().entry(ad.to_string()).or_insert(0) += 1;
}
fn kere(defter: &Defter, ad: &str) -> u32 {
*defter.borrow().get(ad).unwrap_or(&0)
}
fn main() {
let defter: Defter = Rc::new(RefCell::new(BTreeMap::new()));
// tuketilmeyen: kursun olcum cekirdegindeki ayni kesit, hic tuketilmiyor
let _tembel = vec![1, 2, 3, 4].into_iter().map(|x| {
say(&defter, "tuketilmeyen-zincir");
x * 2
});
// ayni kaynak, bu kez toplama cagrisiyla tuketiliyor
let toplam: i32 = vec![1, 2, 3, 4]
.into_iter()
.map(|x| {
say(&defter, "tuketilen-esdeger-zincir");
x * 2
})
.sum();
println!("tuketilmeyen govde kac kez kosti: {}", kere(&defter, "tuketilmeyen-zincir"));
println!(
"tuketilen govde kac kez kosti: {} (toplam {})",
kere(&defter, "tuketilen-esdeger-zincir"),
toplam
);
}
tuketilmeyen govde kac kez kosti: 0 tuketilen govde kac kez kosti: 4 (toplam 20)
İki zincirin kaynak metni aynı biçimde başlıyor: aynı dört elemanlı kaynak, aynı dönüşüm
gövdesi. Fark yalnızca sonda: birincisi bir adın altında saklanıp bırakılıyor, ikincisi bir
toplama çağrısına veriliyor. tuketilmeyen-zincir sayacı koşumun sonuna kadar sıfırda
kalıyor — kursun bağlayıcı tablosunda bu satır hiç sınıfına düşüyor. Eşdeğer zincir
tüketildiğinde ise dönüşüm gövdesi dört elemanın her biri için bir kez, toplam dört kez
koşuyor. Kurulmuş bir zincirin kaynakta durması, sözleşmenin sırada bir sonraki değeri iste
çağrısının hiç gelmediği anlamına geliyor; çağrı gelmeden gövde koşmuyor.
Bu iki koşumun tek farkı zincirin kaynak metninde değil, zincirin sonunda ne bulunduğunda.
.sum() çağrısı, sözleşmenin yöntemini kaynak bitene kadar tekrar tekrar çağıran bir
tüketicidir; _tembel adının altına bırakılan zincirde böyle bir tüketici hiç yok. Bir
zincirin dönüşüm gövdesini yazmak, o gövdeyi koşum defterine kaydetmenin yalnızca ön
koşuludur — yeterli koşulu, zincirin ucuna bir tüketici bağlanmasıdır.
Bu fark, kaynak metni okuyarak da fark edilebilir: _tembel adı altını çizen alt çizgi,
Rust’ın “bu ad kullanılmıyor” uyarısını susturmak için oradadır — kapanışın önceki derste
görülen _kapanis adıyla aynı işaret. Bir zincirin kurulup hiç kullanılmaması, kaynak
metinde de küçük bir iz bırakıyor: adı okunmayan bir bağlama. Tüketilen zincirde ise sonuç
toplam adına bağlanıp yazdırma çağrısında okunuyor; ad kullanılmadan kalmıyor.
Sınırlayıcı Ölçüm
Tüketilmeyen bir zincirin gövde koşum sayacının sıfır olması, o zincirin bedelsiz olduğu
anlamına gelmez. map çağrısı, kaynağı ve dönüşüm kapanışını saran bir yapı kuruyor; bu
yapı _tembel adı altında bellekte duruyor, tıpkı önceki dersteki hiç çağrılmayan bir
kapanışın kendisi gibi. Kurulan bu yapının kendisi bir değerdir ve bir yer kaplar — ama bu
yer kaplama, koşum defterinin ölçtüğü “gövde kaç kez koştu” sorusunun kapsamına girmez.
Defter yalnızca dönüşüm kapanışının içindeki çağrıyı sayıyor; zincirin kurulma anında
kapladığı yapıyı saymıyor. Bu ders o bedeli ölçmeye kalkmıyor, yalnızca var olduğunu
kaydediyor. Bu, önceki dersin sınırlayıcı ölçümüyle birebir aynı gözlemin, tek bir
kapanıştan bir kapanış zincirine taşınmış hâlidir.
Aynı sınır, Sayici örneğindeki kısmi tüketim ölçümü için de geçerli. Sınırı beş olan bir
kaynağın yalnızca iki değeri istenip geri kalan üçünün hiç istenmemesi, o üç değerin
üretilmediği anlamına geliyor — ama kaynağın kendisinin kurulma bedeli, bu sınırın nerede
kesildiğinden bağımsız olarak zaten ödendi. Ölçülen sayı, kurulma anındaki değil, sonraki
istekteki maliyeti yansıtıyor.
Bu iki gözlem birlikte, kaç kez koştu sorusunun sınırlarını çiziyor: sayaç, sözleşmenin yönteminin ne zaman çağrıldığını kesin olarak söylüyor, ama o yöntemi barındıran yapının ne zaman kurulduğunu, ne kadar yer kapladığını söylemiyor. Bir zincir kurulur, bir kısmı istenir, kalanı hiç istenmeden bırakılır — üç ayrı an, üç ayrı soru ve bu dersin ölçtüğü sayı bunlardan yalnızca birine, çağrı anına yanıt veriyor. Kurulma anını ve yer kaplamayı ölçmek isteyen bir soru, farklı bir araç ister; bu kurs o aracı yazmıyor, yalnızca hangi sorunun bu sayının kapsamı dışında kaldığını açıkça belirtiyor.
Özet
- Bir kaynak üzerinde dolaşmak, kaynağın kendi sözleşmesinin tek bir yöntemine tekrar tekrar uğramaya indirgeniyor; üç elemanlı bir kaynakta bu yöntem dört kez çağrılıyor.
- Sözleşmenin kendisi kaynağın saklanma biçiminden bağımsızdır; yalnızca “bir sonrakini ver” sorusuna nasıl yanıt vereceğini tanımlar.
- Kursun ölçüm çekirdeğindeki
tuketilmeyen-zincirgövdesi bu koşumda hiç çalışmadı — aynı kaynağın tüketilen eşdeğeri dört kez çalıştı. - Zincir kurmak ile zincirin ilerlemesi ayrı olaylardır; kurulma anında sözleşmenin yöntemine hiç uğranmaz; beş elemanlık bir kaynağın yalnızca iki değeri istenirse sözleşmenin yöntemi de yalnızca iki kez çağrılır.
- Tembel değerlendirmenin kendisi bu derste yeniden kurulmadı; yalnızca kaç kez koştuğu sayıldı.
- Kurulmuş ama tüketilmeyen bir zincir bedelsiz değildir — bu bedel gövde koşum sayacının ölçtüğü şey değildir; sayaç yalnızca çağrı anını, kurulma anını değil ölçer.
Sonraki Adım
Bu derste bir zincirin hiç tüketilmemesi ile tüketilmesi arasındaki fark, tek bir dönüşüm adımı üzerinden ölçüldü: sıfıra karşı dört. Ama “tüketilmek” tek bir anlam taşımıyor — kısmi tüketim ölçümünde görüldüğü gibi, bir kaynağın ne kadarının istendiği de sözleşmenin yöntemine kaç kez uğranacağını belirliyor. Bu derste iki uç durum görüldü: hiç istenmemek (sıfır) ve tamamı istenmek (dört). Sıradaki ders aynı kaynağı, aynı dönüşüm gövdesini alıp zincirin ucuna farklı tüketiciler koyuyor ve bu kez zincirin uzunluğunun değil, tüketicinin gövdenin kaç kez koşacağını belirlediğini gösteriyor — bir tüketici kaynağın tamamını isteyebilir, bir başkası ilk uygun değeri bulunca durabilir; ikisi de aynı dört elemanlı kaynaktan başlayacak, ama biri uçlardan birine, öbürü ortasında bir yere düşecek bir sayı üretecek. Bu iki ders bir arada, “tüketmek” sözcüğünün aslında bir uçtan öbürüne uzanan bir aralığı kapsadığını gösteriyor: hiç, kısmen, tamamen.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.