İçeriğe geç
academia.sh

Ders 09 / 15

Hata Yayılımının Kısa Yazımı

Bir hatayı denetleyip erken dönmeyi tek bir işarete indirgeyen kısa yazımın, aynı işi elle yazılan bir dallanmayla birebir aynı sayıları verdiğinin — kaynağı kısalttığının, koşumu değiştirmediğinin — ölçülmesi.

Yayın Güncelleme

Bu derste

  • Soru işaretinden sonraki gövdenin başarılı çözümlemede bir kez, başarısızda hiç çalışmadığını koşturarak gösterir
  • Kısa yazımın elle yazılan dallanmayla aynı sayıları verdiğini karşılaştırarak doğrular
  • Erken dönüşün kalan gövdeyi sessizce atladığını ve atlanan adımın yan etkisi olmadığını örnekle ayırt eder
İçindekiler

Başarısızlığın Dönüş Değeri dersi, aynı işlevin art arda çağrıldığında başarı ve başarısızlık kollarının birbirinden bağımsız çalıştığını, her ikisinin de aynı match gövdesinde üç satırlık bir düzeni tekrarladığını gösterdi: değeri ayrıştır, hata varsa bildir, yoksa devam et. Bir işlev birden çok ayrıştırma yapıyorsa bu düzen aynı biçimde art arda tekrarlanır — her ayrıştırmadan sonra aynı iki kollu match. Bu ders, bu tekrarı tek bir işarete indirgeyen kısa yazımı ölçüyor: soru işareti (?).

Bu konunun üçüncü dersi olarak, bu ders artık başarı ve başarısızlığın ayrı gövdeler olduğunu varsayıyor — o ayrım önceki derste kuruldu. Burada sorulan soru bir adım öteye geçiyor: bu iki gövdeyi ayıran match iskeleti, kaynakta ne kadar kısalabilir ve bu kısalma koşumdaki sayıları değiştirir mi? Yanıt, aşağıdaki iki koşumun karşılaştırılmasıyla ortaya çıkıyor.

Arayüzler, Hatalar ve Genellikler kursu (M09/K02) bir hatanın bir dönüş değeri olarak taşındığını, kontrol akışının dışına çıkmadığını kurmuştu. Soru işareti de bu kararı bozmuyor — hâlâ bir dönüş değeri üretiyor, hâlâ normal fonksiyon dönüşü kullanıyor — yalnız o dönüşü yazmanın kaynaktaki uzunluğunu kısaltıyor. Bu ders M09/K02’nin ölçtüğü nerede sorusuna dönmüyor; ölçtüğü, kısa yazımın koşumu değiştirip değiştirmediği.

Fonksiyonlar ve İfade Odaklılık dersi (M10/K01), bir işlevin gövdesi bitmeden erken dönmenin ne zaman gerektiğini ölçmüştü: gövdenin geri kalanı hesaplanmayacaksa, açık bir return bu durumu kaynakta görünür kılıyordu. Soru işareti tam olarak bu erken dönüşü kullanıyor — yalnız return yazımını elle yazmak yerine, bir Result çağrısının hemen ardına tek bir işaret koyarak. Bu ders o dersin kurduğu erken dönüş kavramını yeniden kurmuyor; yalnız bu belirli kısa yazımın, elle yazılan bir erken dönüşle aynı sayıları verip vermediğini ölçüyor.

Aynı Sayılar, Daha Kısa Kaynak

Soru işareti, bir Result döndüren bir işlevin içinde, başka bir Result döndüren bir çağrının hemen ardına yazılır. Çağrı Ok dönerse, içindeki değer açılır ve gövde kaldığı yerden devam eder; Err dönerse, işlev o anda kendi Erriyle döner ve gövdenin geri kalanı hiç çalışmaz.

Bu kısayolun bir koşulu var: soru işareti yalnız, içinde bulunduğu işlevin dönüş tipi de bir Result (ya da Option) olduğunda yazılabilir. main işlevinin dönüş tipi () ise, mainin gövdesinde bir Result üreten bir çağrının ardına doğrudan ? konamaz — erken dönüşün döneceği bir Result yuvası yok. Bu ders bu koşulu ihlal etmiyor: cozumle kendi Result dönüş tipini taşıyor, ? yalnız onun gövdesinde kullanılıyor; main içindeki çağrılar ise dönen Resultı bir değişkene bağlayıp {:?} ile yazdırıyor, kendisi ? kullanmıyor.

Aşağıdaki cozumle işlevinde metin.parse() bir Result<i32, ParseIntError> döndürür, ama işlevin kendi dönüş tipi Result<i32, SayiHatasi>dir — iki farklı hata tipi. Soru işareti bu farkı kendi başına çözer: SayiHatasi için yazılan impl From<ParseIntError> gerçekleştirimi sayesinde, parsein ürettiği hata otomatik olarak SayiHatasie dönüştürülür. Bu dönüşümün kendisi Özel Hata Tipleri dersinin konusu; bu derste yalnız ?nin bu dönüşümü kullandığı, ama bu derste ölçülen sayıyı etkilemediği görülüyor.

// sorulu.rs — kere.rs'in cozumle kesiti: basarili columdan sonrasi bir kez, basarisizda hic kosuyor
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)
}

#[derive(Debug)]
struct SayiHatasi;

impl From<std::num::ParseIntError> for SayiHatasi {
    fn from(_: std::num::ParseIntError) -> SayiHatasi {
        SayiHatasi
    }
}

fn cozumle(defter: &Defter, metin: &str, etiket: &str) -> Result<i32, SayiHatasi> {
    let sayi: i32 = metin.parse()?;
    say(defter, etiket);
    Ok(sayi * 2)
}

fn main() {
    let defter: Defter = Rc::new(RefCell::new(BTreeMap::new()));

    let basarili = cozumle(&defter, "21", "soru-sonrasi-basarili");
    let basarisiz = cozumle(&defter, "yirmibir", "soru-sonrasi-basarisiz");

    println!("basarili cagri: {:?}", basarili);
    println!("basarisiz cagri: {:?}", basarisiz);
    println!();
    for ad in ["soru-sonrasi-basarili", "soru-sonrasi-basarisiz"] {
        println!("{:<24}{} kez", ad, kere(&defter, ad));
    }
}
basarili cagri: Ok(42)
basarisiz cagri: Err(SayiHatasi)

soru-sonrasi-basarili   1 kez
soru-sonrasi-basarisiz  0 kez

HT7. cozumle(&defter, "21", ...) çağrısında metin.parse() başarıyla 21i üretiyor; soru işareti bu değeri açıyor, sayiya bağlıyor ve gövde kaldığı yerden devam ediyor — say çağrısı çalışıyor, satır soru-sonrasi-basarili bir kez artıyor. cozumle(&defter, "yirmibir", ...) çağrısında ise parse bir hata üretiyor; soru işareti bu hatayı SayiHatasie çeviriyor ve işlevden hemen dönüyor. say çağrısına hiç sıra gelmiyor — soru-sonrasi-basarisiz sıfırda kalıyor. Bu, önceki dersteki Ok/Err kollarının aynı davranışı: biri çalışıyor, öbürü çalışmıyor. Fark, bu iki kolun artık ayrı yazılmış bir match içinde değil, tek bir işaretin arkasında saklı olması.

cozumle işlevinin dönüş tipine bakıldığında, gövdesinde yalnız bir satır (let sayi: i32 = metin.parse()?;) Result ile ilgili görünüyor; geri kalan iki satır (say çağrısı ve Ok(sayi * 2)) sıradan kod gibi okunuyor. Bu, soru işaretinin kaynakta bıraktığı izin ne kadar küçük olduğunu gösteriyor — bir işlev, kaç ayrı ayrıştırma yapıyorsa yapsın, her ayrıştırmanın ardına tek bir karakter ekleyerek hata denetimini tamamlıyor. etiket parametresinin saye geçirilen adı taşıması, bu derste iki farklı çağrıyı (başarılı ve başarısız) aynı işlevin üzerinden, ama ayrı sayaç anahtarlarıyla izlemeyi sağlıyor; gerçek bir kullanımda bu parametre bulunmaz, yalnız ölçüm amacıyla eklendi.

Aynı işlev elle yazılan bir dallanmayla da kurulabilir. Aşağıdaki koşum, soru işaretini match ve erken return ile değiştiriyor ve aynı iki girdiyle çağrılıyor.

// elle_dallanma.rs — ayni is elle dallanmayla yazilinca da ayni sayilari veriyor
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)
}

#[derive(Debug)]
struct SayiHatasi;

fn cozumle_elle(defter: &Defter, metin: &str, etiket: &str) -> Result<i32, SayiHatasi> {
    let ayristirma = metin.parse::<i32>();
    let sayi = match ayristirma {
        Ok(deger) => deger,
        Err(_) => return Err(SayiHatasi),
    };
    say(defter, etiket);
    Ok(sayi * 2)
}

fn main() {
    let defter: Defter = Rc::new(RefCell::new(BTreeMap::new()));

    let basarili = cozumle_elle(&defter, "21", "soru-sonrasi-basarili");
    let basarisiz = cozumle_elle(&defter, "yirmibir", "soru-sonrasi-basarisiz");

    println!("basarili cagri: {:?}", basarili);
    println!("basarisiz cagri: {:?}", basarisiz);
    println!();
    for ad in ["soru-sonrasi-basarili", "soru-sonrasi-basarisiz"] {
        println!("{:<24}{} kez", ad, kere(&defter, ad));
    }
}
basarili cagri: Ok(42)
basarisiz cagri: Err(SayiHatasi)

soru-sonrasi-basarili   1 kez
soru-sonrasi-basarisiz  0 kez

HT8. İki koşum birebir aynı sayıları veriyor: soru-sonrasi-basarili bir, soru-sonrasi-basarisiz sıfır. Kaynak farklı — ilkinde tek satır (metin.parse()?), ikincisinde beş satırlık bir match — ama koşum aynı. Soru işareti yeni bir davranış eklemiyor; elle yazılan match ve erken returnün derleyicinin ürettiği koduyla aynı işi, daha az yazarak yapıyor. Bu, kısa yazımın koşumu değil kaynağı kısalttığının doğrudan kanıtı: iki program farklı uzunlukta kaynağa sahip, ama aynı ikili davranışı üretiyor.

cozumle_elle işlevindeki match, önceki dersteki matchten bir farkla ayrılıyor: Err kolu bir değer üretip gövdenin geri kalanına devretmiyor, doğrudan return Err(SayiHatasi) ile işlevden çıkıyor. Bu, Denetim Akışı dersinde (M10/K01) kurulan matchin bir ifade olması ilkesiyle çelişmiyor — return bir ifade değil bir deyim ve bir match kolunun içinde bir deyim yazılabilir; o kolun ürettiği “değer” gövdenin geri kalanına hiç ulaşmıyor, çünkü işlevin kendisinden çıkılıyor. Ok kolu ise sıradan bir değer (deger) üretiyor ve bu değer sayiya bağlanıyor, gövde oradan devam ediyor. İki kolun farklı doğası — biri işlevden çıkıyor, öbürü bir değer veriyor — soru işaretinin arkasında da aynen duruyor, yalnız görünmez.

Kaynaktaki kısalmanın gerçek bedeli, bir işlev birden çok ayrıştırma yaptığında görülür. Üç ayrı alanı sırayla ayrıştıran bir işlev, elle yazılan dallanmayla üç kez tekrarlanan beş satırlık bir match (toplam on beş satır) gerektirirdi; aynı işlev soru işaretiyle üç satıra iner — her alanın ardına tek bir işaret. Bu derste ölçülen iki koşum (bir satırlık kısa yazım, beş satırlık elle yazım) bu farkın en küçük örneği; alan sayısı arttıkça kaynaktaki fark da büyür, ama her iki yazımın da ürettiği soru-sonrasi-basarili / soru-sonrasi-basarisiz çifti aynı kalır. Kısa yazımın büyümesi doğrusal değil — kaynak satır sayısı azalırken, koşumun kendisi hiç değişmez.

Soru işareti Option üzerinde de aynı biçimde çalışır: içinde bulunduğu işlev Option dönüyorsa, Some içindeki değeri açar, None durumunda işlevden hemen None döndürür. Bu ders bu ikinci kullanımı koşturmuyor — Yokluğun Tiple İfadesi dersinde ölçülen Option yöntemleri bir değer üretme (unwrap_or, unwrap_or_else) üzerine kuruluydu, burada ölçülen ise erken dönüş. İki kullanım da aynı işareti taşısa da, biri bir değer üretir (unwrap_or_elsein yaptığı gibi), öbürü işlevden çıkar; bu derste ölçülen yalnız Result üzerindeki, işlevden çıkan biçimi.

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

Yukarıdaki koşumda soru-sonrasi-basarili ve soru-sonrasi-basarisiz her zaman birlikte hareket ediyor: biri bir olduğunda öbürü sıfır, biri sıfır olduğunda öbürü bir. Bu, tek bir gözlemin değil, aynı satırın iki farklı çağrıdaki durumunun sonucu — ikisi de aslında aynı say(defter, etiket) çağrısını temsil ediyor, yalnız etiket argümanı çağırana göre değişiyor.

HT9. Erken dönüş, kendisinden sonraki gövdeyi sessizce atlıyor — ne bir uyarı yazdırıyor ne bir işaret bırakıyor. "yirmibir" girdisiyle yapılan çağrıda say çağrısı hiç çalışmadığı için sayaç defterine hiçbir kayıt düşmüyor; bu, “adım atlandı” diye bir iz bırakmıyor, yalnız beklenen kaydın yokluğuyla görünüyor. Atlanan adımın kendisi bir yan etki üretmiş olsaydı — bir dosyaya yazma, bir sayacı artırma, bir günlük satırı basma — o yan etki de gerçekleşmezdi. Bu ders bunu tam olarak say çağrısının kendisiyle gösteriyor: say, atlanan adımın tek yan etkisi ve o yan etki gerçekleşmediği için sayı sıfırda kalıyor.

Bu, önceki dersteki Err kolunun kaynakta yazılı ama koşmamış olmasından farklı bir durum. Orada iki kol da aynı matchin içinde açıkça duruyordu, ikisi de görünürdü. Burada “atlanan gövde” ayrı bir kol olarak yazılmıyor — soru işaretinden sonraki her satır, hata durumunda örtük olarak atlanıyor. Kaynağı okuyan biri, ?den sonraki satırların hangi koşulda çalışacağını görebilir, ama derleyicinin bunun için ayrı bir kol ürettiğini kaynakta görmez; bu kol yalnız koşumda, sayaçla ölçüldüğünde ortaya çıkıyor.

Bu sessizlik bir tasarım kararının sonucu, bir kusur değil. Soru işareti bir uyarı yazdırmıyor, çünkü hatanın kendisi zaten dönüş değeri olarak taşınıyor — çağıran taraf bu değeri denetlediği sürece, “neden atlandığı” bilgisi kayıp değil, yalnız gövdenin geri kalanında değil, dönüş değerinde duruyor. Bu ders bu bilgiyi izlemiyor; yalnız gövdenin geri kalanının çalışıp çalışmadığını sayıyor. Bir işlevin cozumleyi çağıran tarafı, dönen Resultı denetlemezse, hem bu bilgiyi hem de neyin atlandığını görme fırsatını kaybeder — ama bu, soru işaretinin değil, çağıran tarafın denetlememe kararının sonucu.

Aynı sessiz atlama, işlev sınırlarını art arda geçen bir soru işareti zincirinde de aynı biçimde tekrarlanır. Bir işlev kendi içinde soru işaretiyle başka bir işlevi çağırıyorsa ve o işlev de kendi içinde bir üçüncü işlevi soru işaretiyle çağırıyorsa, en alttaki başarısızlık üç ayrı gövdeyi de sessizce atlayarak en üste kadar yükselir — hiçbiri bir iz bırakmadan. Bu ders bu çok katmanlı zinciri koşturmuyor; yalnız tek bir sınırda, atlamanın kendisinin sessiz olduğunu gösteriyor.

Bu üç katmanlı zinciri düşünmek sınırı daha da netleştiriyor. En alttaki işlev bir ayrıştırma yapıyor ve başarısız oluyor diyelim; onu çağıran orta katman kendi gövdesinde üç satır daha çalıştıracaktı, ama hiç çalıştırmıyor; en üstteki çağıran ise beş satır daha çalıştıracaktı, o da çalıştırmıyor. Toplamda sekiz satırlık gövde, tek bir başarısızlık yüzünden sessizce atlanmış oluyor — ama kaynakta bu sekiz satırın “atlanabilir” olduğunu işaretleyen hiçbir yorum, hiçbir özel söz dizimi yok. Yalnız her üç katmanın da kendi dönüş tipinin Result olması ve her katmanın çağrısının ardında bir soru işareti bulunması, bu sessiz atlamanın mümkün olduğunu gösteriyor.

Bu konunun ilk iki dersinde ölçülen ayrım burada da geçerliliğini koruyor: Optionda hazır ve tembel gövdeler arasındaki fark, Resultda başarı ve başarısızlık kolları arasındaki fark, burada da aynı biçimde duruyor — yalnız artık match kolları elle yazılmıyor, tek bir işaretin ardında saklanıyor. Kısa yazım, ölçülen üç sınıfın (her seferinde, yalnız gerektiğinde, hiç) hiçbirini ortadan kaldırmıyor; yalnız o sınıfları taşıyan kaynağı küçültüyor.

Özet

  • Soru işareti, bir Result çağrısının ardından yazılan erken dönüşü tek bir işarete indirger; Ok dönerse değeri açar ve gövde devam eder, Err dönerse işlev hemen döner.
  • Soru işaretinden sonraki gövde, çağrı başarılı olduğunda yalnız gerektiğinde çalışır — çağrı başarısız olduğunda hiç çalışmaz.
  • Aynı işlev elle yazılan bir match ve erken return ile kurulduğunda aynı sayıları verir: kısa yazım koşumu değil, kaynağı kısaltır — bu fark, bir işlev birden çok ayrıştırma yaptıkça büyür.
  • Erken dönüş kalan gövdeyi sessizce atlar; atlanan adımın hiçbir yan etkisi de gerçekleşmez — ne bir uyarı, ne bir kısmi kayıt.
  • Atlanan gövde kaynakta ayrı bir kol olarak görünmez; yalnız sayaçla ölçüldüğünde, eksik kalan kayıtla fark edilir.
  • Soru işareti farklı hata tiplerini From ile otomatik dönüştürür; bu dönüşümün kim tarafından çağrıldığı sıradaki dersin konusudur.

Sonraki Adım

Yukarıdaki SayiHatasi, std::num::ParseIntErrori kendi tipine dönüştüren bir From gerçekleştirimi taşıyordu — soru işaretinin bu dönüşümü nasıl çağırdığı bu derste gösterilmedi, yalnız kullanıldı. cozumle işlevinin gövdesinde bu dönüşümü çağıran açık bir satır yok; metin.parse()? yazımının kendisi hem hatayı denetliyor hem de gerekirse dönüştürüyor. Özel Hata Tipleri dersi bu dönüşümü kimin çağırdığını ölçüyor: kısa yazım bu çağrıyı çağıran taraf hiçbir şey yazmadan mı yapıyor, yoksa elle yazılan bir dönüşüm mü gerekiyor — ve iki yaklaşım aynı bilgiyi taşıyabiliyor mu, yoksa örtük dönüşüm bir şeyi kaybediyor mu?

İ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