İçeriğe geç
academia.sh

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-zincir gö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.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat