Forum: D Programlama Dili RSS
İsimli şablon kısıtlaması yöntemi
acehreli (Moderatör) #1
Kullanıcı başlığı: Ali Çehreli
Üye Haz 2009 tarihinden beri · 4527 mesaj
Grup üyelikleri: Genel Moderatörler, Üyeler
Profili göster · Bu konuya bağlantı
Konu adı: İsimli şablon kısıtlaması yöntemi
Yeni öğrendiğim ve ilginç bulduğum bir yöntemi anlatacağım. Çok uzun oldu; kusura bakmayın! :)

Yöntem şu:

template uçabilir_mi(T)
{
    const bool uçabilir_mi = is (typeof(
    {
        T uçan;            // kurulabilmeli
        uçan.hazırlan();   // uçmaya hazırlanabilmeli
        uçan.uç(1);        // belirli bir mesafe uçabilmeli
        uçan.kon();        // istendiğinde konabilmeli
        uçan.anlat();      // başına gelenleri anlatabilmeli
    }()));
}

(Not: O yöntemi std.range modülünde tanımlanmış olan isInputRange'den uyarladım. Böyle kullanımlar Phobos'ta başka modüllerde de var.)

Baştan bana çok garip gelmişti. Dikkat ederseniz, typeof'un içinde küme parantezli bazı kodlar var. O küme parantezlerinden hemen sonra da açılıp kapanan işlev parantezleri var: (). O kodda neler olduğunu anlayabiliyor musunuz? Benim anlamam biraz zaman aldı... :)

Bu çok sayıda D olanağının birlikte kullanan bir yöntem. Baştan anlamamıştım; çünkü anlamak için gereken her şey Digital Mars'ın sitesinde bile yok (veya ben henüz bulamadım). Olsa da, hiç böyle kullanılacağı aklıma gelmezdi. Üstelik bilmecenin bir parçasını da uykuda çözdüm! :)

Ek olarak, bu yöntemim kesinlikle gerekmediğini de söylemem gerekiyor; ama her usta D'cinin en azından anlaması gerekir.

Yukarıdakilere bakıp da dünyanın en önemli kodlama yöntemi olduğunu da sanmayın lütfen. :) Sadece ilginç bulduğum için yazıyorum. Önemli de değil; günlük programcılıkta da çok kullanılacağını sanmam.

Kullanılan D olanakları:

- şablonlar ddili.org/ders/d/sablonlar.html
- tek tanım içeren şablonlar (şu sıralarda yazmaktayım)
- şablon kısıtlamaları (şu sıralarda yazmaktayım)
- is ifadesi: ddili.org/ders/d/kosullu_derleme.html
- typeof ifadesi: (belirli bir derste anlatmadım ama çok derste geçti)
- isimsiz kapamalar: ddili.org/ders/d/kapamalar.html

Önce sorunu anlayalım. Elimizde şöyle bir işlev şablonu olsun:

void kullan(T)(T nesne)
{
    nesne.hazırlan();
    // ... başka işlemler ...
    nesne.uç(42);
    // ... başka işlemler ...
    nesne.kon();
    // ... başka işlemler ...
    nesne.anlat();
}

O şablonun her türle çağrılabileceği, ama derlenemeyeceği açık: ancak ve ancak şablon içinde çağrılan o dört üye işlevi olan türlerle kullanılabilir.

Örneğin şu türle kullanılabilir:

class ModelUçak
{
    void hazırlan()
    {}
 
    void(int mesafe)
    {}
 
    void kon()
    {}
 
    void anlat()
    {}
}

Ama şu türle kullanılamaz:

class Güvercin
{
    void(int mesafe)
    {}
}
 
void main()
{
    auto taklacı = new Güvercin;
    kullan(taklacı);
}

Güvercin ile kullanıldığında derleme hataları alırız:

Error: no property 'hazırlan' for type 'deneme.Güvercin'
Error: no property 'kon' for type 'deneme.Güvercin'
Error: no property 'anlat' for type 'deneme.Güvercin'

Güzel, ama sorunlu...

Sorun şu: derleyici, o hatalar için şablonun içindeki satırlara işaret eder. Yani derleyicinin gözünde hata, şablonun içindeki örneğin nesne.hazırlan() çağrısındadır. Bu, C++ şablonlarının da büyük bir sorunudur. Siz bir kod yazarsınız, ondan sonra derleyici standart kütüphanenin bir başlık dosyasının bir satırının derlenemediğini bildirir. Kullanışlı değildir... :/

Oysa yukarıdaki asıl hata; kullan'ın, main içinde uygun olmayan bir türle çağrılmasındadır. Doğru olan, derleyicinin o satırda hata vermesidir ama öyle olmamaktadır.

D'de bunun yolu, şablon kısıtlamaları (template constraints) tanımlamaktır. (Bu konu henüz D.ershane'de yok; şu günlerde yazıyorum.) Şablon kısıtlamaları, şablonun kapsamı ile isim satırı arasındaki bir if ifadesiyle belirtilir.

Örneğin yalnızca ve yalnızca uzunluğu 8 bayt olan türlerle çalışabilen bir algoritmamız olsun. Bunu, bir şablon kısıtlaması olarak şöyle ifade edebiliriz:

void algoritma(T)(T nesne)
    if (T.sizeof == 8)       // <-- şablon kısıtlaması
{
    // ...
}
 
void main()
{
    algoritma(42L);   // derlenir (long 8 bayttır)
    algoritma(1.25)// derlenir (double 8 bayttır)
 
    short[4] dizi;
    algoritma(dizi)// derlenir (bu dizi 4 * 2 == 8 bayttır)
 
    algoritma(42);    // <-- DERLEME HATASI (int 4 bayttır)
}

Yukarıdaki kısıtlama, derleyicinin o şablonu yalnızca uzunluğu 8 bayt olan türler için göze almasını sağlar. Şablon o kısıtlamaya uymayan türlerle kullanılamaz.

O yüzden, şablonun yukarıdaki int kullanımı bir derleme hatasına neden olur:

Error: template deneme.algoritma(T) if (T.sizeof == 8) cannot deduce template function from argument types !()(int)

Şimdi güzel olan, o hatanın main içindeki kullanıma işaret etmesidir. Yani artık hata, şablonun içindeki bir kodda değil, o şablonu uygunsuz bir türle çağıran programcıdadır. Güzel: yani şablon kısıtlamaları bu işe yarıyor.

Şimdi asıl konuya dönelim ve yukarıdaki uçan nesne kullanan kullan şablonu için bir kısıtlama yazmaya çalışalım. O şablonun yalnızca o dört üye işlevi sunan türlerle kullanılabildiğini, derleyiciye (ve programcıya) güzel bir dille anlatmak istiyoruz.

Şu çalışır:

void kullan(T)(T nesne)
    if (is (typeof(T.hazırlan)) &&
        is (typeof(T.uç)) &&
        is (typeof(T.kon)) &&
        is (typeof(T.anlat)))
{
    // ...
}

O kısıtlama, derleme hatasının istediğimizi gibi, main'deki kullanıma işaret etmesini sağlar:

    auto taklacı = new Güvercin;
    kullan(taklacı);   // <-- DERLEME HATASI 

Tamam: amacımıza ulaştık. Ancak, kısıtlamalar bazen anlaşılır olamayabilirler ve buradakinden daha karmaşık olabilirler. Bazen, yukarıdaki kısıtlama yerine, tam olarak derdimizi anlatan bir söz kullanmak daha kullanışlı olabilir:

void kullan(T)(T nesne)
    if (uçabilir_mi!T)
{
    // ...
}

İşte, yazının başında verdiğim uçabilir_mi şablonu o işe yarar. Artık bir bakışta bu şablonun uçabilen türlerle kullanılması gerektiği anlaşılır.

En sonunda, bütün bu yazının amacına geldim: uçabilir_mi'nin nasıl çalıştığını anlatmak istiyordum. Yararlanılan D olanaklarını sıralayacağım:

Tek tanım içeren template blokları: Eğer şablon bloğunda tek tanım varsa, ve o tanımın ismi şablonun ismi ile aynıysa; o şablon doğrudan içindeki tanım anlamına gelir. Kötü bir örnek olsa da, bunu şu şablonda görebiliriz:

import std.stdio;
 
template birDeğişken(T)
{
    T birDeğişken;
}
 
void main()
{
    birDeğişken!int = 42;
    birDeğişken!double = 1.2;
 
    writeln(birDeğişken!int, ' ', birDeğişken!double);
}

O kodda örneğin birDeğişken!int, int birDeğişken ile aynı şeydir. (Evet kötü bir örnek, ama bu kadarı yeterli...)

İşte uçabilir_mi şablonunun içindeki aynı isimdeki bool, o şablonun bir bool olarak kullanılabilmesini sağlar.

Şablon kısıtlaması: Şablonun içinde tanımlanan uçabilir_mi isimli bool'un değeri ile belirleniyor

is ifadesi: Buradaki is ifadesi, kendisine verilen türün geçerli bir tür olup olmadığına göre true veya false üretiyor

typeof ifadesi: typeof, kendisine verilen ifadeyi hiçbir zaman işletmez, ama onun türünü üretir. Benim şimdiye kadar bilmediğim bir özelliği daha varmış: typeof'a aslında tamamen geçersiz veya yanlış kodlar da verilebiliyormuş:

    writeln(is(typeof(var_olmayan_bir_işlev())));

Yukarıdaki satır derleme hatasına neden olmaz ve çıkışa false yazdırır. Bunun nedeni, typeof'un içindeki kodun derlenemeyecek olduğu için geçersiz bir sonuç üretmesidir. is de kendisine geçersiz tür geldiğinde false üretir

İsimsiz kapama: uçabilir_mi şablonu içindeki { ... }, bir isimsiz kapama tanımlar. Ondan hemen sonra yazılan () parantezleri, o isimsiz kapamayı hemen oracıkta işletir. typeof da o işletmenin sonucunun türünü üretir. (Tabii aslında o kapama, typeof içinde olduğu için hiç işletilmez.)

Sonuç: Eğer o kapama, şablon parametresi olan T için derlenebiliyorsa,

- typeof geçerli bir tür üretir (void de geçerli bir türdür)

- geçerli bir tür aldığı için, is'in değeri true olur

- o true, uçabilir_mi isimli bool'u ilkler

- şablonun ismi ile içindeki bool aynı olduklarından, if (uçabilir_mi!T) gibi bir kısıtlama yazılabilir

Bitti. :)

Ali

Not: Aslında uçabilir_mi diye bir çözüm kullanmak yerine, UçanNesne isminde bir arayüz tanımlanabilir ve kullan'ın işlev parametresi olarak onu alması da sağlanabilir:

interface UçanNesne
{
    void hazırlan();
    void(int);
    void kon();
    void anlat();
}
 
void kullan(UçanNesne nesne)
{
    // ...
}

Böylece, yalnızca o arayüzden türeyen türler kullanılabilir. Zaten arayüzlerin amacı da budur. Yani o da olur.

Arayüzlü çözüm, sınıf sıradüzeni kullanan bir çözümdür. Sınıflar, çalışma zamanı çokşekilliği getirirler ve bu konuda çok yararlıdır.

Şablonlar ise derleme zamanı çokşekilliği (compile time polymorphism) olanaklarıdır.

Bunlar iki farklı programlama yöntemidir... Ama artık iyice bu yazının dışına çıkıyor... :)
Doğrulama Kodu: VeriCode Lütfen resimde gördüğünüz doğrulama kodunu girin:
İfadeler: :-) ;-) :-D :-p :blush: :cool: :rolleyes: :huh: :-/ <_< :-( :'( :#: :scared: 8-( :nuts: :-O
Özel Karakterler:
Bağlı değilsiniz. · Şifremi unuttum · ÜYELİK
This board is powered by the Unclassified NewsBoard software, 20100516-dev, © 2003-10 by Yves Goergen
Şu an: 2017-11-19, 04:17:15 (UTC -08:00)