Bunu bilmiyordum. Ya sonradan eklenmiş, ya da şurada 'Foreach over Delegates' başlığı altındaki iki satırlık (!) bilgiyi gözden kaçırmış olmalıyım: ;)
http://www.d-programming-language.org/statement.html#ForeachStatement
foreach'in temel olanaklarla nasıl işlediğini biliyoruz:
http://ddili.org/ders/d/foreach_dongusu.html
foreach'i kendi türlerimiz için de tanımlayabiliyoruz:
http://ddili.org/ders/d/foreach_opapply.html
Orada anlatıldığına göre,
-
basit durumlarda aralık işlevleri kullanmak akıllıca
-
gerektiğinde ise türün opApply() işlevini tanımlıyoruz
opApply() yüklenebiliyor; foreach döngüsünün değişken tanımlarına uygun olan opApply() seçiliyor. Böylece geçende konuştuğumuz gibi ve yukarıdaki bölümün problemlerinin 2 numaralısında da olduğu gibi belirli bir topluluk üzerinde farklı biçimlerde ilerleyebiliyoruz.
Orada Öğrenci ve Öğretmen türlerinin farklı olmalarından yararlanılıyor. Oysa foreach değişkenlerinin aynı türlerde oldukları için opApply()'ın yüklenemeyeceği durumlar olabilir. Sanırım bu yüzden opApply()'ı delegate'ler üzerinde de kullanma olanağı sağlamışlar. delegate'leri "kapamalar" adı altında şurada anlatmıştım (ama bu isimden memnun değilim çünkü delegate'lerin ancak belirli bir kullanımı fonksiyonel dillerdeki kapamaların (closure) eşdeğeri oluyor):
http://ddili.org/ders/d/kapamalar.html
foreach ile delegate kullanmanın gerekebileceği şöyle bir örnek düşünebiliriz. (İhtiyaç duymayınca örnek bulmak zor. :)) Düzlemdeki noktaların x,y koordinatlarından oluşan Noktalar diye bir tür olsun. Örneğin bir poligonun noktalarını barındırıyor olabilir.
foreach'i bu tür için "x değerleri üzerinde" veya "y değerleri üzerinde" ilerleyecek biçimde ancak delegate ile kullanabiliyoruz çünkü aralık işlevleri kullanıldığında front() belirli türden bir değer döndürür ve o tür de doğal olarak Nokta'dır.
opApply() yüklendiğinde ise hangisinin kullanılacağı foreach'in değişkenlerinin türüne bağlı olduğundan ve ne yazık ki x ve y ikisi de int olduklarından opApply() yüklemesi de kullanamıyoruz.
O yüzden burada çözüm, Noktalar türüne iki farklı üye işlev eklemek(miş :)). Bu üye işlevler opApply() ile aynı kimliğe sahip delegate'ler döndürmeliler(miş). Kulağa çok karmaşık geliyor, çünkü opApply() zaten delegate aldığı için bu üye işlevlerin "parametre olarak delegate alan delegate" döndürmeleri gerekiyor! :)
Ne kadar zor gibi gelse de, aslında dikkatle bakınca yine de bir opApply() yüklemesi gibi görünüyor ama bu yüklemeleri sanki farklı isimde üye işlevler içine gizliyoruz...
Bu iki üye işlevin döndürdüğü delegate'lerden birisi x değerlerini, diğeri de y değerlerini kullanacak.
import std.stdio;
struct Nokta
{
int x;
int y;
}
struct Noktalar
{
Nokta[] noktalar;
this(Nokta[] noktalar)
{
this.noktalar = noktalar;
}
/*
* Burada dönüş türü olarak auto yazmak çok uygun. Çünkü dönüş türü aslında
* şu kadar karmaşık:
*
* int delegate(int delegate(ref int) işlemler)
*
* (Tabii alias kullanarak yazımını biraz olsun kolaylaştırabilirdik de.)
*/
@property auto x_değerleri()
{
/* Burada 'işlemler', foreach bloğunu temsil ediyor */
return delegate int(int delegate(ref int değer) işlemler) {
int sonuç;
foreach (nokta; noktalar) {
sonuç = işlemler(nokta.x); // <-- x değerleri
if (sonuç) {
break; // foreach bloğu içinde 'break'e rastlamışız demek
}
}
return sonuç;
};
}
/*
* Bu işlev yukarıdakinin aynısı. Tek farkı, nokta.x yerine nokta.y yazılmış
* olması.
*/
@property auto y_değerleri()
{
return delegate int(int delegate(ref int değer) işlemler) {
int sonuç;
foreach (nokta; noktalar) {
sonuç = işlemler(nokta.y); // <-- y değerleri
if (sonuç) {
break;
}
}
return sonuç;
};
}
/*
* Karşılaştırmak amacıyla, Nokta değerlerini döndüren bir opApply()'ı da
* şöyle yazabilirdik. Yukarıdakilerin buna ne kadar benzediğine dikkat
* edin. Yukarıdaki fark, sanki opApply()'ın bir üye işlevden döndürülüyor
* olması gibi düşünülebilir.
*/
int opApply(int delegate(ref Nokta nokta) işlemler) {
int sonuç;
foreach (nokta; noktalar) {
sonuç = işlemler(nokta); // <-- noktalar
if (sonuç) {
break;
}
}
return sonuç;
}
}
void main()
{
auto noktalar = Noktalar([ Nokta(1,2),
Nokta(3,4),
Nokta(5,6) ]);
writeln("x değerleri:");
foreach (değer; noktalar.x_değerleri) {
writeln(değer);
}
writeln("y değerleri:");
foreach (değer; noktalar.y_değerleri) {
writeln(değer);
}
writeln("noktalar:");
foreach (nokta; noktalar) {
writeln(nokta);
}
}
Çıktısı:
'x değerleri:
1
3
5
y değerleri:
2
4
6
noktalar:
Nokta(1, 2)
Nokta(3, 4)
Nokta(5, 6)
'
Üç işlev neredeyse aynı; tek farkları 'işlemler' delegate'ini nasıl çağırdıkları. Burada kodu biraz düzenleyebiliriz. Şimdi kod tekrarı azalıyor ama belki de kodun anlaşılması güçleşiyor:
struct Noktalar
{
Nokta[] noktalar;
this(Nokta[] noktalar)
{
this.noktalar = noktalar;
}
int noktaDöngüsü(T, İ)(İ işlemler, T delegate(Nokta nokta) erişici)
{
int sonuç;
foreach (nokta; noktalar) {
T değer = erişici(nokta);
sonuç = işlemler(değer);
if (sonuç) {
break;
}
}
return sonuç;
}
@property auto x_değerleri()
{
return delegate int(int delegate(ref int değer) işlemler) {
return noktaDöngüsü!int(işlemler, (Nokta nokta){ return nokta.x; });
};
}
@property auto y_değerleri()
{
return delegate int(int delegate(ref int değer) işlemler) {
return noktaDöngüsü!int(işlemler, (Nokta nokta){ return nokta.y; });
};
}
int opApply(int delegate(ref Nokta nokta) işlemler) {
return noktaDöngüsü!Nokta(işlemler, (Nokta nokta){ return nokta; });
}
}
Başım ağrıyor! :-p (Şaka!)
Ali
--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]