İçeriğe geç
academia.sh

Ders 14 / 15

Yineleyici Bağdaştırıcıları

Aynı dört elemanlı kaynaktan başlayan iki zincirin kaynak metni neredeyse aynı, ama biri gövdeyi her elemanda, öbürü yalnız gerektiği kadar koşturuyor: sayıyı belirleyenin zincirin uzunluğu değil ucuna bağlanan tüketici olduğu ve zincirlenen bağdaştırıcıların gövdelerinin eleman başına sırayla koştuğu ölçülüyor.

Yayın Güncelleme

Bu derste

  • Aynı kaynak ve aynı dönüşüm gövdesiyle kurulan iki zincirin, yalnızca ucundaki tüketiciye göre farklı sayıda koştuğunu ölçerek gösterir
  • Zincirin uzunluğu ile tüketicinin istediği miktarı birbirinden ayırt eder
  • İki bağdaştırıcı zincirlendiğinde gövdelerin eleman başına sırayla mı yoksa bağdaştırıcı başına toplu mu koştuğunu bir kayıtla ölçer
İçindekiler

Önceki ders bir zincirin hiç tüketilmemesi ile tamamen tüketilmesi arasındaki iki uca baktı: sıfır ve dört. Ama bu iki uç, “tüketilmek”in taşıdığı tüm olasılıkları tüketmiyordu — bir kaynağın tamamı istenmeden de, hiç istenmeden de tüketilebileceği bir orta hâl vardı. Bu ders aynı kaynağı, aynı dönüşüm gövdesini alıp zincirin ucuna farklı tüketiciler bağlıyor ve ortaya çıkan sayının zincirin uzunluğundan değil, tüketicinin ne kadar istediğinden geldiğini gösteriyor. İkinci ölçüm ise tek bir bağdaştırıcıdan iki bağdaştırıcıya geçiyor: iki dönüşüm zincirlendiğinde, gövdeler bir sırayla mı, yoksa bir başka sırayla mı koşuyor — bu sıra kaynak metinde açıkça yazılı değil, yalnızca koşumdan okunabiliyor. Bu konunun adı “bağdaştırıcı” olan map gibi çağrıları anıyor; bu ders o adın neden uygun olduğunu sayarak gösteriyor — bir bağdaştırıcı, kaynağı değiştirmeden onu sarıp aynı sözleşmeyi taşıyan yeni bir kaynağa çeviriyor.

Aynı Kaynak, Farklı Tüketici, Farklı Sayı

Bir dönüşüm zincirinin kaynak metni, zincirin ucuna hangi tüketicinin bağlandığını söylemeden önce kaç kez koşacağını söylemez. Bu, önceki iki dersin ölçülerinin doğal bir birleşimidir: bir kapanışın gövdesi çağrılana kadar koşmuyordu, bir dolaşım sözleşmesi istenene kadar ilerlemiyordu — bu ders bu iki gözlemi birleştirip, “ne kadar istendiğinin” zincirin ucundaki tek bir çağrıyla, tüketiciyle belirlendiğini gösteriyor. Aynı dört elemanlı kaynak ve aynı dönüşüm gövdesiyle kurulan iki zincir, yalnızca uçları farklı olduğu için farklı sayıda koşabilir: biri kaynağın tamamını isteyen bir toplama çağrısına bağlanır, öbürü ilk uygun değeri bulunca duran bir arama çağrısına. Zincirin ortasındaki dönüşüm adımı, hangi tüketicinin kendisini çağıracağını yazarken bilmiyor — bu bilgi yalnızca zincir tamamlanıp çalıştırıldığında ortaya çıkıyor.

YN8. — “aynı dört elemanlı kaynaktan kurulan iki zincirin, ucuna toplama çağrısı bağlanan gövdenin dört, ucuna ilk uygun değeri arayan çağrı bağlanan gövdenin iki kez koştuğu” iddiası, aşağıdaki koşumla doğrulanıyor.

// tuketilen_ve_erken_biten.rs — kursun olcum cekirdegindeki kesit: zincirin kaynagi ayni, sonu farkli
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()));

    // tuketilen zincir: her elemanda
    let toplam: i32 = vec![1, 2, 3, 4]
        .into_iter()
        .map(|x| {
            say(&defter, "tuketilen-zincir");
            x * 2
        })
        .sum();

    // erken biten zincir: yalniz gerektigi kadar
    let bulunan = vec![1, 2, 3, 4]
        .into_iter()
        .map(|x| {
            say(&defter, "erken-biten-zincir");
            x * 2
        })
        .find(|x| *x > 2);

    println!(
        "tuketilen zincir govdesi kac kez kosti: {} (toplam {})",
        kere(&defter, "tuketilen-zincir"),
        toplam
    );
    println!(
        "erken biten zincir govdesi kac kez kosti: {} (bulunan {:?})",
        kere(&defter, "erken-biten-zincir"),
        bulunan
    );
}
tuketilen zincir govdesi kac kez kosti: 4 (toplam 20)
erken biten zincir govdesi kac kez kosti: 2 (bulunan Some(4))

İki zincirin ilk satırı birebir aynı yapıdadır: aynı dört elemanlı kaynak, aynı x * 2 dönüşümü. .sum() tüm kaynağı istiyor, bu yüzden dönüşüm gövdesi dört elemanın her biri için bir kez koşuyor — kursun bağlayıcı tablosunda bu satır her seferinde sınıfına düşüyor: tüketici kaynağın tamamını istediği sürece, gövde her elemanda kaçınılmaz olarak çalışıyor. .find(|x| *x > 2) ise ilk uyan değeri arıyor: kaynak sırasıyla 2, 4, 6, 8 üretirken arama ikinci elemanda (4 > 2) duruyor, üçüncü ve dördüncü elemana hiç uğranmıyor. Bu satır yalnız gerektiğinde sınıfına düşüyor — iki değil dört elemanın tamamı üretilebilirdi, ama tüketici ikinci değerden sonra durmayı seçti; gövde yalnızca tüketicinin sonucu istediği kadar koştu. Kaynağın uzunluğu ikisinde de dörttü; koşan gövde sayısı ikisinde de farklı çıktı. 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 gövdenin kaç kez koştuğudur.

Zincirin kaynak metnindeki tek fark tüketicinin adıdır — sum ya da find. Dönüşüm gövdesinin kendisi, hangi tüketicinin onu çağıracağını bilmiyor ve bilmesi gerekmiyor: sözleşmenin yöntemi ne kadar istenirse o kadar çalışıyor, kalanını hiç görmüyor. Zincire üçüncü, dördüncü bir dönüşüm adımı eklense bile, bu ölçü değişmezdi — belirleyici olan adım sayısı değil, tüketicinin kaç kez “bir sonrakini ver” dediği. Aynı dönüşüm gövdesi, kaynak metninde hiç değişmeden, yalnızca zincirin ucundaki tek sözcük (sum yerine find, find yerine başka bir arama ya da sayma çağrısı) değiştirilerek dört, iki, bir ya da önceki derste görülen sıfır dahil sıfır ile dört arasında herhangi bir sayı üretebilir.

YN9. — “aynı dönüşüm gövdesinin, ilk elemanın işlenmiş hâli aranan değere hemen uyduğunda yalnızca bir kez koştuğu” iddiası, aşağıdaki koşumla doğrulanıyor.

// uc_tuketici.rs — ayni govde farkli bir tuketiciyle uculu farkli bir sayi verir
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()));

    let bulundu = vec![1, 2, 3, 4]
        .into_iter()
        .map(|x| {
            say(&defter, "ilk-elemanda-duran-zincir");
            x * 2
        })
        .any(|x| x == 2);

    println!(
        "ilk elemanda duran zincir kac kez kosti: {} (bulundu mu: {})",
        kere(&defter, "ilk-elemanda-duran-zincir"),
        bulundu
    );
}
ilk elemanda duran zincir kac kez kosti: 1 (bulundu mu: true)

.any(|x| x == 2), dönüştürülmüş ilk değerin (1 * 2 = 2) aranan değere zaten uyduğunu görüp ikinci elemana hiç uğramadan duruyor. Aynı dönüşüm gövdesi bu üçüncü tüketiciyle bir kez koştu — ne tuketilen-zincir ölçümündeki dört, ne erken-biten-zincir ölçümündeki iki. Üç koşum bir arada — bir, iki, dört ve önceki dersten sıfır — aynı gövdenin yalnızca tüketici değiştirilerek dört ayrı sayı üretebildiğini gösteriyor; sayı gövdenin değil tüketicinin özelliğidir. Aranan değer ilk elemanda değil de son elemanda uysaydı, .any da tıpkı .find gibi kaynağın tamamını dolaşırdı — durma noktası, tüketicinin tanımından değil, aranan koşulun kaynakta nerede karşılandığından da etkileniyor.

Zincirlenen Bağdaştırıcılar Eleman Başına Sırayla Koşuyor

İki dönüşüm adımı art arda zincirlendiğinde, akla gelen iki olası sıralama vardır: ya birinci adım kaynağın tamamını işleyip bir ara sonuç üretir, sonra ikinci adım bu ara sonucun tamamını işler (bağdaştırıcı başına toplu); ya da her eleman önce birinci adımdan, hemen ardından ikinci adımdan geçer, sonra bir sonraki elemana geçilir (eleman başına sırayla). Bu iki olasılık kaynak metinden ayırt edilemez — .map(a).map(b) yazımı ikisiyle de tutarlı görünür; hangisinin gerçekleştiğini yalnızca koşum söyler.

Bir önceki bölümde kurulan sözleşme burada da geçerli: zincirin tek bir “bir sonrakini ver” yöntemi var ve bu yöntem zincirin en dışındaki adımdan çağrıldığında, çağrı zincirin içine doğru yayılıyor. En dıştaki adım kendi bir sonraki değerini istediğinde, bunun için önce kendinden önceki adımdan bir değer istemesi gerekiyor; o adım da kendinden öncekinden istiyor — zincir boyunca bu istek geriye doğru yayılıp bir kaynak değeriyle geri dönüyor. Bu yayılma her seferinde tek bir eleman için oluyor, kaynağın tamamı için değil — bu da ikinci olasılığı, eleman başına sırayı, işaret ediyor.

Her map çağrısı, kendi başına, kaynağın sözleşmesini gerçekleştiren yeni bir değer üretiyor — bir yineleyiciyi sarıp aynı sözleşmeyi taşıyan başka bir yineleyici. İkinci map çağrısı, birinci mapin ürettiği bu yeni değeri kaynak olarak alıyor; kendisi de aynı sözleşmeyi taşıyan üçüncü bir değer üretiyor. Zincirin en dışındaki tüketici bir sonraki değeri istediğinde, aslında bu iç içe geçmiş üç sözleşmenin en dışındakini çağırıyor; o da kendi kaynağından (ikinci mapin ürettiği değer) bir sonrakini istiyor, o da kendi kaynağından. Bu iç içe geçme, aşağıdaki koşumda tutulan kayıtla doğrudan gözlemlenebiliyor.

YN10. — “iki bağdaştırıcı zincirlendiğinde, birinci adımın bir elemanı işlemesinin hemen ardından ikinci adımın aynı elemanı işlediği, ikinci elemana geçmeden önce tamamlandığı” iddiası, aşağıdaki koşumda tutulan sıra kaydıyla doğrulanıyor.

// bagdastirici_sirasi.rs — iki bagdastirici zincirlendiginde eleman basina sirayla kosuyor
use std::cell::RefCell;
use std::rc::Rc;

type Kayit = Rc<RefCell<Vec<String>>>;

fn kaydet(kayit: &Kayit, metin: String) {
    kayit.borrow_mut().push(metin);
}

fn main() {
    let kayit: Kayit = Rc::new(RefCell::new(Vec::new()));
    let kayit_a = Rc::clone(&kayit);
    let kayit_b = Rc::clone(&kayit);

    let sonuc: Vec<i32> = vec![1, 2, 3]
        .into_iter()
        .map(move |x| {
            kaydet(&kayit_a, format!("birinci-asama-{x}"));
            x + 1
        })
        .map(move |x| {
            kaydet(&kayit_b, format!("ikinci-asama-{x}"));
            x * 2
        })
        .collect();

    println!("sonuc: {:?}", sonuc);
    println!("sira: {}", kayit.borrow().join(", "));
}
sonuc: [4, 6, 8]
sira: birinci-asama-1, ikinci-asama-2, birinci-asama-2, ikinci-asama-3, birinci-asama-3, ikinci-asama-4

Kayıt sırası, bağdaştırıcı başına toplu değil eleman başına sırayla koştuğunu açıkça gösteriyor: birinci-asama-1 (birinci adımın ilk elemanı işlemesi) ile ikinci-asama-2 (ikinci adımın aynı sonucu hemen işlemesi) art arda geliyor, ikinci elemanın işlenmesi üçüncü satırda başlıyor. Eğer koşum bağdaştırıcı başına toplu olsaydı, kayıt önce üç birinci-asama-* satırı, sonra üç ikinci-asama-* satırı olarak gelirdi — koşum bunu göstermiyor. Her eleman, zincirin başından sonuna kadar tek bir yolculuk yapıyor; bir sonraki eleman bu yolculuk bitmeden başlamıyor.

Kayıt tutmak için burada say/kere çifti yerine paylaşımlı bir kayıt kabı (Kayit) kullanıldı, çünkü bu ölçüm sayı değil sıra istiyor — kaç kez koştuğu değil, hangi sırayla koştuğu önemli. İki dönüşüm kapanışı, aynı kayıt kabını Rc::clone ile ayrı ayrı paylaşıyor; ikisi de aynı kaba yazabiliyor, çünkü kap kendi içinde değiştirilebilirliğini taşıyor. Bu düzenek, Sahiplik ve Bellek Modeli kursunda kurulan paylaşımlı sahiplik kurallarının burada da geçerli olduğunu gösteriyor — bu ders o kuralları yeniden kurmuyor, yalnızca bir kayıt tutmak için kullanıyor.

Zincire üçüncü bir bağdaştırıcı eklense, aynı düzen sürerdi: her eleman üç adımdan da sırayla geçer, dördüncü elemana geçmeden önce üçüncü adım da tamamlanırdı. Zincirin uzunluğu sırayı değiştirmiyor, yalnızca her elemanın kaç durak yapacağını belirliyor.

Bu sıralama, ilk bölümde ölçülen erken durma davranışıyla da tutarlı: eğer zincirin sonuna .find gibi erken duran bir tüketici bağlansaydı, üçüncü elemana hiç ulaşılmadan zincir dururdu — ama ulaşılan her eleman yine de baştan sona, tüm adımlardan sırayla geçerdi. Erken durma zincirin kaç eleman işlediğini sınırlıyor; eleman başına sıralama ise işlenen her bir elemanın zincirde nasıl ilerlediğini belirliyor. İkisi aynı sözleşmenin, “bir sonrakini ver” çağrısının, farklı iki sonucu.

Sınırlayıcı Ölçüm

İki bağdaştırıcının aynı elemanı art arda işlemesi, adımların birbirinden habersiz olduğu anlamına gelmiyor — sıralama gözlemlenebilir olsa da, bu sıralamayı zincirin kaynak metninden çıkarım yapıp tahmin etmek yerine bir kayıtla göstermek gerekti; kaynak metindeki iki ardışık map çağrısının yazımı bu sırayı doğrudan söylemiyor. İkinci sınır, bu dersin ilk ölçümünde görülen: iki zincirin dördü de kaynaktan üretilebilirdi, ama biri ikisinde durdu. Bu durma kararı zincirin bir yerinde yazılı değil — tüketicinin kendi tanımında. Aynı dönüşüm gövdesi, farklı bir tüketiciyle zincirlenseydi farklı bir sayı üretirdi; bu ders o sayının zincirin kendisinden değil tüketiciden geldiğini gösterdi, ama her tüketicinin ne zaman durduğunu genel bir kuralla vermiyor — her tüketici kendi durma koşulunu taşıyor.

Üçüncü bir sınır, ölçüm aracının kendisiyle ilgili: her iki koşumda da dönüşüm gövdesine bir kayıt çağrısı eklendi, ki bu çağrı olmasaydı gövdenin kaç kez koştuğu hiç gözlemlenemezdi. Kayıt çağrısının kendisi ölçülen işin bir parçası değildir — dönüşümün asıl işi x * 2dir, kayıt yalnızca bir gözlem aracıdır. Bu araç eklenince gövdenin kendi maliyeti artmış olabilir, ama sayının kendisi (dört, iki) kayıt çağrısı olmasaydı da aynı kalırdı; ölçüm, ölçtüğü şeyi değiştirmeden gözlemliyor.

Son bir sınır, bu konunun üç dersinin tümüne birden uzanır: ölçülen her sayı tek bir koşumdan geldi. Kaynağın eleman sayısı, dönüşüm gövdesi ve tüketici sabit tutulduğu sürece bu sayı koşumdan koşuma değişmiyor — kaynak dört elemanlı bir dizi olduğu, map içindeki gövde x * 2 olduğu ve .find(|x| *x > 2) her seferinde aynı sırayla sorguladığı sürece, gövde her koşumda tam olarak iki kez koşacak. Bu belirlenimcilik, önceki konulardaki ölçümlerin dayandığı aynı varsayımdır ve burada da bozulmuyor.

Özet

  • Aynı kaynak ve aynı dönüşüm gövdesiyle kurulan iki zincir, yalnızca uçlarındaki tüketiciye göre farklı sayıda koşabilir: biri her seferinde (dört elemanın hepsinde), öbürü yalnız gerektiğinde (ilk uyan bulununca durarak) koştu.
  • Zincirin uzunluğu sabit kaldı (dört eleman); koşan gövde sayısı sabit kalmadı (dört, iki, bir) — belirleyici olan tüketicidir, zincirin kendisi değil.
  • Dönüşüm gövdesi hangi tüketicinin onu çağıracağını bilmez; sözleşmenin yöntemi ne kadar istenirse o kadar çalışır.
  • İki bağdaştırıcı zincirlendiğinde gövdeler bağdaştırıcı başına toplu değil, eleman başına sırayla koşuyor: bir eleman zincirin başından sonuna kadar tek bir yolculuk yapıyor, sonra bir sonraki eleman başlıyor.
  • Bu sıralama kaynak metinden doğrudan okunmuyor; bir kayıtla gözlemlenmesi gerekiyor.
  • Kayıt tutmak için kullanılan paylaşımlı kayıt kabı, Sahiplik ve Bellek Modeli kursunda kurulan paylaşımlı sahiplik kurallarının bir uygulamasıdır; bu kurallar burada yeniden kurulmadı, yalnızca bir gözlem aracı olarak kullanıldı.

Sonraki Adım

Bu konuda bir kapanışın tek başına ne zaman koştuğu, bir dolaşım sözleşmesinin bu koşmayı nasıl tek bir yönteme indirdiği ve bir zincirin ucundaki tüketicinin bu sayıyı nasıl belirlediği ölçüldü. Üç ders birlikte, “kaynakta yazdığın bu gövde kaç kez koştu” sorusunu giderek büyüyen birimlere uyguladı: tek bir kapanıştan, tek bir sözleşmeye, sözleşmeler zincirine. Kursun kapanış dersi aynı soruyu somut bir veri biçimine taşıyor: bir yapının alanlarına uygulanan bir dönüşüm gövdesi, yapı serilirken alan başına kaç kez koşuyor. Orada zincirin elemanları artık bir dizinin girdileri değil, tek bir yapının alanları olacak — eleman sayısı sabit ve küçük, tüketici tektir (serileştirmenin kendisi) — ama sayan aynı soru: kaynakta yazılan bu gövde kaç kez koştu. Bu konuda öğrenilen ayrım orada da geçerli kalacak: bir gövdenin kaynakta yazılı olması onun koştuğu anlamına gelmiyor, kaç kez koştuğu da hep aynı sabit sayı olmak zorunda değil — belirleyici, gövdeyi kimin ve ne zaman çağırdığıdır.

İ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