İçeriğe geç
academia.sh

Ders 01 / 15

Trait Tanımı ve Gerçekleştirimi

Bir trait, tipten bağımsız bir davranış sözleşmesi kurar ve varsayılan bir gövde taşıyabilir; on altı gövdelik bir ölçüm çekirdeği koşturulduğunda bu varsayılan gövdenin bir tip için dört kez, onu yeniden yazan bir tip için sıfır kez koştuğu görülür — kurs boyunca sorulacak soru budur: kaynakta yazdığın bu gövde kaç kez koştu?

Yayın Güncelleme

Bu derste

  • Trait tanımının bir davranış sözleşmesi kurduğunu ve varsayılan gövde taşıyabildiğini gösterir
  • Ölçüm çekirdeğini koşturarak varsayılan bir gövdenin bir tip için her seferinde, onu yeniden yazan tip için hiç koşmadığını doğrular
  • trait ile arayüz kavramlarını birbirinden ayırır ve M09/K02 ile ölçü eksenindeki farkı açıklar
İçindekiler

Sahiplik ve Bellek Modeli kursu bir değerin ömrünün nerede bittiğini ölçtü: kapsam sonunda mı, taşındığı yerde mi, açık bir çağrıda mı, yoksa bir kabıyla birlikte mi. Bir değer ömrü boyunca elinde kalırken, onunla ne yapıldığı ayrı bir sorudur ve bu kurs oraya bakıyor. Bir davranışı bir tipten ayırmak — aynı işlevi birden fazla tipe aynı adla tanıtmak — o davranışın gövdesinin kaç kez çalıştığını da belirleyen bir karar haline geliyor. Kurs boyunca tek bir soru soruluyor: kaynakta yazdığın bu gövde kaç kez koştu?

Bu soruya üç yanıt var ve kurs boyunca bu üç ad değişmiyor.

Üç Sınıf

Sınıf Tanım
her seferinde Çağrı ya da eleman başına kaçınılmaz olarak koşuyor
yalnız gerektiğinde Yalnız sonucu istendiğinde, gerektiği kadar koşuyor
hiç Kaynakta yazılı, ama bu koşumda bir kez bile koşmadı

Bu üçlü daha önce sorulan iki soruyla karıştırılmamalı. Nesneye Dayalı Java kursu kararı hangi tipin verdiğini ölçtü — bildirilen tip mi, çalışma zamanı tipi mi. Arayüzler, Hatalar ve Genellikler kursu (Go) bağın nerede kurulduğunu ölçtü — bildirimde mi, kullanım yerinde mi, değerin içinde mi. Bu kurs ikisini de sormuyor; saymayı soruyor. Bir trait gerçekleştirimi yazıldığında bağ zaten kurulmuş olur — bu derste ölçülen, o bağ kurulduktan sonra gövdenin çalışma zamanında kaç kez tetiklendiğidir.

Bir trait, bir davranış sözleşmesidir: bir ya da daha fazla yöntem imzası bildirir, bu yöntemlerin tipten bağımsız bir adla çağrılabilmesini sağlar. Programlama Temelleri kursundaki fonksiyon tanımı tek bir tipe bağlıydı; trait bunu genelliyor — aynı yöntem adı, onu gerçekleştiren her tip için ayrı bir gövdeye karşılık gelebiliyor. Bu, arayüz (interface) kavramına yakın görünse de aynı şey değil: arayüz, Go kursunda bağın nerede kurulduğu ekseninde tanımlandı (örtük gerçekleştirim, bildirim gerektirmeden); trait ise Rust’ta bir tipin impl Ad for Tip yazımıyla açıkça bildirdiği bir sözleşmedir ve üstelik varsayılan bir gövde taşıyabilir. Kurs boyunca trait sözcüğü çevrilmeden kullanılacak.

Zorunlu ve İsteğe Bağlı Yöntemler

Bir trait bildirimi, trait Ad { ... } gövdesinin içinde bir dizi yöntem imzası sıralar. Her imza iki biçimden birini alabilir: yalnız bir başlık (fn ad(&self) -> Tip;, noktalı virgülle biten, gövdesiz) ya da bir başlık artı bir gövde (fn ad(&self) -> Tip { ... }). Birinci biçim zorunludur: trait’i gerçekleştiren her tip bu yöntemi kendi impl bloğunda mutlaka yazmak zorundadır, aksi halde gerçekleştirim tamamlanmamış sayılır ve kaynak derlenmez. İkinci biçim isteğe bağlıdır: gerçekleştiren tip bu yöntemi hiç yazmayabilir, yazmazsa trait’in kendi gövdesi devreye girer; yazarsa kendi gövdesi çalışır ve trait’inki hiç görünmez.

Bu ders boyunca kullanılan Etiket trait’i üçünü birden gösteriyor. defteri ve tip gövdesiz bildirilmiş — her gerçekleştirim bu ikisini kendi yazmak zorunda, çünkü trait’in kendisi bir tipin defterine ya da adına dair hiçbir varsayımda bulunamaz. ad ise bir varsayılan gövdeyle bildirilmiş: Sade bu yöntemi hiç yazmıyor ve trait’in gövdesini miras alıyor, Ozel ise kendi gövdesini yazıp varsayılanı örtüyor. Aynı trait bildirimi içinde zorunlu ve isteğe bağlı yöntemler bir arada durabiliyor; bu, bir sözleşmenin kimi maddelerinin her imzalayan için farklı olmak zorunda olduğunu (adres, isim gibi), kimi maddelerinin ise ortak bir varsayılanla başlayıp yalnız gerekince değiştirilebileceğini söylemesine benziyor.

impl Etiket for Sade yazımının kendisi de iki parçaya ayrılıyor: Etiket, hangi sözleşmenin gerçekleştirildiğini söylüyor; Sade, sözleşmeyi kimin gerçekleştirdiğini. Aynı trait, farklı tipler için ayrı impl bloklarıyla defalarca gerçekleştirilebilir — kere.rsde Sade ve Ozel için iki ayrı impl Etiket for ... bloğu bulunuyor, ikisi de aynı trait adını taşıyor ama gerçekleştirdikleri gövdeler bağımsız.

Ölçüm Çekirdeği

Kursun geri kalanında kesitleri alınacak çekirdek kere.rsdir. On altı ayrı gövdeyi tek koşumda sınıflandırır ve her birini bir gözlem defterine kaydeder. Defter, önceki kursun paylaşımlı sahiplik dersinde kurulan düzeneği kullanıyor: Rc<RefCell<BTreeMap<String, u32>>> — birden fazla sahibi olan, iç değişebilirlik taşıyan, sıralı bir sayaç.

// kere.rs — bir govde kac kez kostu: her seferinde mi, yalniz gerektiginde mi, hic mi.

use std::cell::RefCell;
use std::collections::BTreeMap;
use std::ops::Add;
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)
}

// --- trait: varsayilan govde ve onu yeniden yazan tip ---

trait Etiket {
    fn defteri(&self) -> &Defter;
    fn tip(&self) -> &'static str;
    fn ad(&self) -> String {
        let anahtar = format!("varsayilan-govde-{}", self.tip());
        say(self.defteri(), &anahtar);
        String::from("varsayilan")
    }
}

struct Sade {
    defter: Defter,
}
struct Ozel {
    defter: Defter,
}

impl Etiket for Sade {
    fn defteri(&self) -> &Defter {
        &self.defter
    }
    fn tip(&self) -> &'static str {
        "sade"
    }
}

impl Etiket for Ozel {
    fn defteri(&self) -> &Defter {
        &self.defter
    }
    fn tip(&self) -> &'static str {
        "ozel"
    }
    fn ad(&self) -> String {
        say(self.defteri(), "yeniden-yazilan-govde");
        String::from("ozel")
    }
}

// --- genellik ile trait nesnesi: ayni is, iki cagri bicimi ---

fn statik<T: Etiket>(x: &T) -> String {
    say(x.defteri(), "statik-gonderim");
    x.ad()
}

fn dinamik(x: &dyn Etiket) -> String {
    say(x.defteri(), "dinamik-gonderim");
    x.ad()
}

// --- islec asiri yukleme ---

#[derive(Clone)]
struct Uzunluk {
    deger: i32,
    defter: Defter,
}

impl Add for Uzunluk {
    type Output = Uzunluk;
    fn add(self, oteki: Uzunluk) -> Uzunluk {
        say(&self.defter, "islec-govdesi");
        Uzunluk {
            deger: self.deger + oteki.deger,
            defter: Rc::clone(&self.defter),
        }
    }
}

// --- hata yolu ---

#[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 hazir_varsayilan(defter: &Defter) -> i32 {
    say(defter, "hazir-varsayilan");
    -1
}

fn main() {
    let defter: Defter = Rc::new(RefCell::new(BTreeMap::new()));
    let mut sinif: Vec<(&str, &str)> = Vec::new();

    // 1-2. varsayilan govde: yeniden yazmayan tipte kosuyor, yazan tipte hic kosmuyor
    let sade = Sade {
        defter: Rc::clone(&defter),
    };
    let ozel = Ozel {
        defter: Rc::clone(&defter),
    };
    let _ = sade.ad();
    let _ = ozel.ad();
    let _ = ozel.ad();

    // 3-4. ayni is iki cagri biciminde
    let _ = statik(&sade);
    let _ = statik(&sade);
    let _ = dinamik(&sade as &dyn Etiket);
    let _ = dinamik(&ozel as &dyn Etiket);

    // 5. islec govdesi
    let a = Uzunluk {
        deger: 3,
        defter: Rc::clone(&defter),
    };
    let b = Uzunluk {
        deger: 4,
        defter: Rc::clone(&defter),
    };
    let toplam = a.clone() + b;
    let toplam = toplam + a;
    let _ = toplam.deger;

    // 6. hazir varsayilan: deger VARKEN bile kosuyor
    let dolu: Option<i32> = Some(7);
    let _ = dolu.unwrap_or(hazir_varsayilan(&defter));

    // 7-8. tembel varsayilan: deger varken hic, yokken bir kez
    let bos: Option<i32> = None;
    let _ = dolu.unwrap_or_else(|| {
        say(&defter, "tembel-varsayilan-gereksiz");
        -1
    });
    let _ = bos.unwrap_or_else(|| {
        say(&defter, "tembel-varsayilan-gerekli");
        -1
    });

    // 9-10. soru isaretinden sonrasi
    let _ = cozumle(&defter, "21", "soru-sonrasi-basarili");
    let _ = cozumle(&defter, "yirmibir", "soru-sonrasi-basarisiz");

    // 11. hata donusumu
    if cozumle(&defter, "yirmibir", "yok-sayilan").is_err() {
        say(&defter, "hata-donusumu");
    }

    // 12. tuketilmeyen zincir
    let _tembel = vec![1, 2, 3, 4].into_iter().map(|x| {
        say(&defter, "tuketilmeyen-zincir");
        x * 2
    });

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

    // 14. 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);
    let _ = bulunan;

    // 15. cagrilmayan kapanis
    let _kapanis = || say(&defter, "cagrilmayan-kapanis");

    // --- rapor ---
    let govdeler = [
        ("varsayilan-govde-sade", "her seferinde"),
        ("yeniden-yazilan-govde", "her seferinde"),
        ("statik-gonderim", "her seferinde"),
        ("dinamik-gonderim", "her seferinde"),
        ("islec-govdesi", "her seferinde"),
        ("hazir-varsayilan", "her seferinde"),
        ("tuketilen-zincir", "her seferinde"),
        ("hata-donusumu", "her seferinde"),
        ("tembel-varsayilan-gerekli", "yalniz gerektiginde"),
        ("soru-sonrasi-basarili", "yalniz gerektiginde"),
        ("erken-biten-zincir", "yalniz gerektiginde"),
        ("varsayilan-govde-ozel", "hic"),
        ("tembel-varsayilan-gereksiz", "hic"),
        ("soru-sonrasi-basarisiz", "hic"),
        ("tuketilmeyen-zincir", "hic"),
        ("cagrilmayan-kapanis", "hic"),
    ];

    println!("{:<28}{:<22}{}", "govde", "sinif", "kac kez");
    for (ad, sinifi) in govdeler {
        let n = kere(&defter, ad);
        println!("{:<28}{:<22}{}", ad, sinifi, n);
        sinif.push((ad, sinifi));
    }

    println!();
    for s in ["her seferinde", "yalniz gerektiginde", "hic"] {
        let adet = sinif.iter().filter(|(_, k)| *k == s).count();
        println!("{:<22}{}", s, adet);
    }
    println!("toplam govde: {}", sinif.len());
}
govde                       sinif                 kac kez
varsayilan-govde-sade       her seferinde         4
yeniden-yazilan-govde       her seferinde         3
statik-gonderim             her seferinde         2
dinamik-gonderim            her seferinde         2
islec-govdesi               her seferinde         2
hazir-varsayilan            her seferinde         1
tuketilen-zincir            her seferinde         4
hata-donusumu               her seferinde         1
tembel-varsayilan-gerekli   yalniz gerektiginde   1
soru-sonrasi-basarili       yalniz gerektiginde   1
erken-biten-zincir          yalniz gerektiginde   2
varsayilan-govde-ozel       hic                   0
tembel-varsayilan-gereksiz  hic                   0
soru-sonrasi-basarisiz      hic                   0
tuketilmeyen-zincir         hic                   0
cagrilmayan-kapanis         hic                   0

her seferinde         8
yalniz gerektiginde   3
hic                   5
toplam govde: 16

TR1. Bu koşum belirlenimcidir: beş ayrı koşumda kaynağın aynı on altı satırı, aynı sırayla ve aynı sınıf dağılımıyla üretildiği doğrulandı. On altı gövdenin 8’i her seferinde, 3’ü yalnız gerektiğinde koştu, 5’i hiç koşmadı. Bu sayı kursun geri kalanında değişmiyor; her ders yalnız kendi kesitini alıyor. Rapor bir sıralı sözlük (BTreeMap) kullanıyor — bir karma sözlüğün gezinme sırası koşumdan koşuma değişebileceği için, sayım burada bile sıralamayı güvenceye bağlı bir yapıya bırakıyor.

Varsayılan Gövde İki Tip İçin İki Ayrı Sonuç Veriyor

Etiket trait’i üç yöntem bildiriyor: defteri ve tip hiç gövde taşımıyor — her gerçekleştirim bunları kendisi yazmak zorunda. Üçüncü yöntem, ad, bir varsayılan gövde taşıyor: impl Etiket for Sade bu gövdeye hiç dokunmadığı için, Sade üzerinde ad çağrıldığında trait’in kendi gövdesi çalışıyor. Ozel ise ad‘ı kendi gövdesiyle yeniden yazıyor — trait’teki varsayılan gövde Ozel için hiçbir zaman devreye girmiyor.

Tablodaki iki satır bu ikiliyi doğrudan gösteriyor. varsayilan-govde-sade dört kez koştu: sade.ad() bir kez doğrudan çağrıldı, statik(&sade) iki kez çağrıldı ve her çağrı kendi içinde x.ad()‘ı tetikledi, dinamik(&sade ...) bir kez çağrıldı ve o da aynı şekilde x.ad()‘ı tetikledi — toplam dört çağrı, dördü de trait’in varsayılan gövdesine düştü. yeniden-yazilan-govde ise üç kez koştu: ozel.ad() iki kez doğrudan, dinamik(&ozel ...) bir kez — üçü de Ozelin kendi yazdığı gövdeyi çalıştırdı, trait’in varsayılanına hiç uğramadı. İkisi de her seferinde sınıfına giriyor: çağrıldıkları her yerde kaçınılmaz olarak koşuyorlar, aradaki fark hangi gövdenin koştuğunda.

TR2. Arayüzler, Hatalar ve Genellikler kursu bir gerçekleştirimin bağının nerede kurulduğunu ölçtü — Go’da bir tip bir arayüzü örtük olarak, bildirim yapmadan gerçekleştiriyordu. Rust’ta bu bağ impl Etiket for Sade satırıyla açıkça kuruluyor; kurs bu farkı bir kez kaydedip geçiyor. Burada asıl ölçülen, bağ kurulduktan sonra o gerçekleştirimin gövdesinin koşum sırasında kaç kez tetiklendiği — varsayilan-govde-sade ile yeniden-yazilan-govde arasındaki 4 ile 3 farkı, bağın kendisinden değil çağrı sayısından geliyor.

hazir-varsayilan, islec-govdesi ve hata yolundaki hata-donusumu de aynı sınıfa giriyor — her seferinde koşuyorlar, çünkü çekirdekteki her biri tam olarak çağrıldığı kadar çalışıyor. Kursun ilerleyen dersleri bu satırların her birini ayrı ayrı büyütecek: Trait Sınırları statik-gonderimin sınıra bağlı olup olmadığını, İlişkili Tipler islec-govdesinin imzasındaki Output alanını, Nesne Güvenliği ve Dinamik Gönderim statik-gonderim ile dinamik-gonderimi yan yana koyacak.

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

varsayilan-govde-ozel satırı sıfır. Bu, varsayılan gövdenin var olmadığı anlamına gelmiyor — kaynakta duruyor, trait Etiket bildiriminin içinde, tıpkı Sade için koştuğu haliyle. Sıfır olmasının nedeni, Ozelin kendi ad yöntemini yazarak bu gövdeye hiç uğramaması. Aynı kaynak satırı bir tip için dört kez çalışırken başka bir tip için hiç çalışmıyor; koşum sayısı gövdenin varlığını değil, hangi tiplerin bu gövdeyi kullandığını ölçüyor.

Bu ayrım kursun geri kalanında tekrar karşımıza çıkacak: hiç sınıfına giren beş gövdenin hiçbiri kaynaktan silinmiş değil, beşi de bu koşumda hiç tetiklenmemiş. Bir sayının sıfır olması, “bu kod gereksiz” demek değil; “bu koşumda bu yol seçilmedi” demek.

Varsayılan gövdenin kendisi de bunu görünür kılacak biçimde yazıldı: anahtar dizgesi self.tip() çağrısının döndürdüğü değer bir kalıba yerleştirilerek kuruluyor, sabit bir dizgeyle değil. Trait’in kendi gövdesi, kendisini çalıştıran tipin adını hiç bilmiyor — self.tip() bir dize üretiyor ve gövde onu ödünç alıp anahtara ekliyor. Bu, tek bir varsayılan gövdenin, onu miras alan her tip için ayrı bir satırda sayılabilmesini sağlıyor; Sade ve Ozel aynı varsayılan gövdeyi paylaşsaydı bile (bu örnekte paylaşmıyorlar, çünkü Ozel kendi gövdesini yazdı) iki ayrı anahtar altında ayrı ayrı sayılabilirlerdi. Bu küçük tasarım kararı, çekirdeğin geri kalan derslerde neden kesit alınabilir olduğunu da açıklıyor: her gövde kendi anahtarını taşıyor, bir kesit alındığında geri kalan anahtarlar etkilenmiyor.

Özet

  • Trait, tipten bağımsız bir davranış sözleşmesi kurar ve gerçekleştiren tip yazmazsa devreye giren bir varsayılan gövde taşıyabilir.
  • Ölçüm çekirdeği on altı gövdeyi sınıflandırıyor: 8’i her seferinde, 3’ü yalnız gerektiğinde, 5’i hiç koşuyor; bu sayı kurs boyunca değişmiyor.
  • Aynı varsayılan gövde bir tip için dört kez (her seferinde) koşarken, onu yeniden yazan bir tip için sıfır kez (hiç) koşuyor — gövde kaynakta aynı, koşum sayısı farklı.
  • trait ile arayüz ayrı kavramlardır: arayüz Go’da bağın nerede kurulduğu ekseninde tanımlandı, trait burada açık bildirimle kurulan ve varsayılan gövde taşıyabilen bir sözleşmedir.
  • Sıfır koşum, gövdenin kaynakta yok olduğu anlamına gelmez; o tip için o yola hiç uğranmadığı anlamına gelir.

Sonraki Adım

Trait’i gerçekleştiren bir tipin gövdesi ölçülebildi, ama bir gövdeyi genel bir fonksiyonun içinde kullanabilmek için o fonksiyonun tip parametresine bir sınır koymak gerekiyor. Sıradaki ders bu sınırın koşum sayısına hiç dokunup dokunmadığını sorar — aynı gövde sınırlı ve sınırsız iki biçimde yazılıp koşturularak.

İ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