Alıntı (zekeriyadurmus):
> Txt dosyasından okuduğum kodları parse ettikten sonra bunların bir şemasın çıkarmam gerekiyordu.
O kadarıyla zor bir iş gibi görünmüyor.
Alıntı:
> Ama bu şemayı bir türlü oluşturamadım çünkü pythondaki gibi sonsuz boyutlu birşey yoktu
Oysa D'nin dizileri ve eşleme tabloları (associative array) da sonsuza kadar büyüyebilirler. (?) Ayrıca std.container gibi modüllerde bağlı listeler de var. (Ama std.container'ın modern D'ye daha uygun bir modülle değiştirilmesi de bekleniyor.)
Ama kod örneklerinden anladığıma göre burada aslında bir ağaç veri yapısı da gerekiyor olabilir.
Python'dan farklı olan şu: Herhangi bir topluluğun elemanlarının hep aynı türden olmaları gerekir. Bu, verinin işlendiği noktada önem kazanır. Python bize türler konusunda derleme zamanında hiçbir güvence getirmez. Örneğin, dizide bir int bulunsa bile kod o int'i bir dizi gibi kullanmaya çalışabilir. Bu hata ancak çalışma zamanında ve belki de program uzun bir süre çalıştıktan sonra anlaşılabilir. Hatta, bütün testlerden geçmiş bile olsa program bir kaç ay sonra tek kullanıcı için çalışırken göçebilir.
D gibi strongly statically typed dillerde verinin aynı türden olmasının gerekmesini bir kısıtlama olarak görmemek gerekir. Genel olarak "aynı türden" oldukları halde özel olarak bakıldığında "farklı türden" olan nesneler de bir araya gelebilirler.
D'de bu amacı karşılayan çeşitli olanaklar var. Bunlara bakmadan önce verinin nasıl işleneceğine bakmak gerekir. Metin dosyasından okunan bu verilere genel olarak bir isim verebiliyor muyuz? Örneğin hepsi de Komut mu? Ve bu komutların hepsinin de sağlaması gereken bir işlem var mı? O zaman NYP uygulanabilir:
interface Komut
{
void işlet();
}
class KopyalamaKomutu : Komut
{
string kaynak;
string hedef;
// ...
void işlet()
{
// ... kopyalama işlemi ...
}
}
// Sonsuza kadar büyüyebilen bir Komut dizisi
Komut[] komutlar;
komutlar ~= new KopyalamaKomutu("foo.d", "bar.d");
komutlar ~= new SilmeKomutu("zar.d");
Tahmin ettiğim gibi ağaç gibi dallanabiliyorsa, yani her komutun alt komutları olabiliyorsa o zaman bazı düğümler yalnızca bir Komut[] gibi davranırlar ve böylece ağaç yapısı tamamlanmış olur.
Düşünürsek, böyle bir ağaç yapısında iki tür Komut vardır: ÇokluKomut, TekliKomut. Uyarı: Aşağıdaki program gereğinden karmaşık görünebilir. Daha sonra basitleşecek:
import std.stdio;
// Bu yalnızca bir arayüz. Bu sıradüzenin kullanıcıları en azından işlet()
// işlevini bekleyecekler.
interface Komut
{
public:
void işlet();
}
class TekliKomut : Komut
{
public:
/* Tekli komut işletmek her komutun özel işleticisini çağırmak kadar
* basittir.
*
* Not: Aslında buna gerek yoktur çünkü alt sınıflar işlet_özel()
* işlevinin tanımını vermek yerine doğrudan işlet() işlevinin tanımını da
* verebilirler.
*
* Bu yöntem ise bize bütün tekli komutların işletilmesi sırasında araya
* girebileceğimiz bir olanak sağlar. Örneğin her tekli komut için
* işletilecek olan bir adımı burada gerçekleştirebiliriz.
*
* Bu yönteme 'template method design pattern' denir (ama şablonlarla
* ilgisi yoktur.) Başka bir adı da "non virtual interface"tir.
*/
void işlet()
{
işlet_özel();
}
protected:
// İşte alt sınıflar bunu tanımlamak zorundadırlar
abstract void işlet_özel();
}
class ÇokluKomut : Komut
{
private:
// Kendisi bir Komut olduğu halde birden fazla komuttan oluşuyor
Komut[] komutlar;
public:
// Kurucu bir veya daha fazla komutla başlayabilir
this(Komut[] komutlar...)
{
/* Not: Burada daha normal olarak şöyle yazabilmek isterdim:
*
* this.komutlar = komutlar;
*
* Öyle yapınca program parçalama hatası veriyor çünkü "..." ile
* işaretlenmiş olan parametre gereğinden erken sonlandırılıyor. Bence
* öyle olmamalı. Araştıracağım ve gerekirse bir dmd hatası açacağım.
*
* Ama ~= işleci çalışıyor:
*/
this.komutlar ~= komutlar;
}
// Sonradan da komut eklenebilir
void ekle(Komut komut)
{
komutlar ~= komut;
}
// Çoklu komutun işletilmesi alt komutlarının sırayla işletilmeleridir
void işlet()
{
foreach (komut; komutlar) {
komut.işlet();
}
}
}
// Yukarıdaki temel oluşturulduktan sonra farklı komutların tanımlarına
// geçebiliriz.
// Kopyalama komutu tekli bir komut olsun
class Kopyala : TekliKomut
{
private:
string kaynak;
string hedef;
public:
this(string kaynak, string hedef)
{
this.kaynak = kaynak;
this.hedef = hedef;
}
protected:
override void işlet_özel()
{
writefln("%s dosyasını %s olarak kopyalıyorum", kaynak, hedef);
}
}
// Silme komutu da tekli bir komut olsun
class Sil : TekliKomut
{
private:
string dosya;
public:
this(string dosya)
{
this.dosya = dosya;
}
protected:
override void işlet_özel()
{
writefln("%s dosyasını siliyorum", dosya);
}
}
// Taşımanın başka yolları olsa da, örnek olsun diye taşıma komutunu diğer
// ikisinin bileşimi olarak gerçekleştirelim. Dolayısıyla bu çoklu bir komut
// olacak
class Taşı : ÇokluKomut
{
public:
this(string kaynak, string hedef)
{
// Bunun tek yaptığı, üst sınıfını memnun etmek olacak. O işi de bir
// kopyalama bir de silme komutu vererek gerçekleştiriyoruz:
super(new Kopyala(kaynak, hedef), new Sil(kaynak));
}
// Başka işlem gerekmiyor
}
void main()
{
// Artık sonsuz karmaşıklıkta bir komut ağacı oluşturabiliriz:
Komut[] komutlar;
// Aşağıdaki işlemleri örneğin çalışma zamanında taradığımız bir metin
// dosyasından öğrenebiliriz:
komutlar ~= new Kopyala("bundan_kopyala.txt", "buna_kopyala.txt");
komutlar ~= new Sil("zararli_dosya.txt");
komutlar ~= new Taşı("yanlis_isim.txt", "dogru_isim.txt");
foreach (komut; komutlar) {
komut.işlet();
}
// Bu kadar çeşit komutla bile istediğimiz kadar derine
// dallanabileceğimizi görelim:
auto derinKomut =
new ÇokluKomut(new Sil("duzey0_dosya0"),
new ÇokluKomut(new Sil("duzey1_dosya0"),
new ÇokluKomut(new Sil("duzey2_dosya0"),
new Sil("duzey2_dosya1"))));
derinKomut.işlet();
}
Eğer amaç yukarıdaki Komut gibi temel bir tür değilse veya böyle bir temel zorlama gelecekse o zaman std.variant kullanılabilir. Uyarı: Tabii kimse böyle kod yazmaz, yine dosyadan okunan verinin bir Variant[] dizisine yerleştirildiğini düşünebiliriz:
import std.variant;
void main()
{
Variant[] veriler = [
Variant([ Variant(["a" : Variant("b"),
"x" : Variant([ "c": ["1","2","3"] ] )]),
Variant([ Variant("t") : "1", Variant('y') : "2" ] ) ] )];
}
Alıntı:
> d de çok basit bir sonsuz boyutlu array oluşturamamak projenin önüne koca bir engel koydu.
Bunun başka yolları da var. Ama o zaman güvenliği ve hızı gözardı etmiş oluruz (her ikisi de D'nin temel amaçlarındandır).
Alıntı:
> İstediğim şey sadece
[{"a": "b", "x": {"c": ["1","2","3"]}}, {"t": "1", 'y': "2"}]
gibi içerisinde associative array, array ve string barındırabilen sonsuza dek genişleyebilen bir yapı.
O yapıyı nasıl işleyeceksin? Elemanların teker teker hangi türden olduklarına nasıl karar verebiliriz:
struct EsnekYapı
{
// ...
}
void işle(EsnekYapı[] veri)
{
// Burada ne yapabiliriz? veri[0]'ın asıl türü nedir?
}
void main()
{
EsnekYapı[] veri;
}
Sonsuza kadar büyüyebilen veri yapıları her dilde yapılabilir. Örneğin Python yorumlayıcısının bile C'de yapılabildiğini biliyoruz. C'den daha üstün olan D'de daha da kolay yapılır.
Bizim on sene önce yazılmış bir C kütüphanemiz var. Örneğin orada şöyle bir birlik kullanılıyor. Bu zaten Variant'ın da perde arkasında uyguladığı yöntemdir. (D kodu olarak gösteriyorum):
import std.stdio;
enum Tür { intT, doubleT }
struct Veri
{
Tür tür;
union
{
int i;
double d;
// ... desteklenen diğer türler ...
}
}
void işle(Veri veri)
{
final switch (veri.tür) with (Tür) {
case intT:
writeln("int işliyorum: ", veri.i);
break;
case doubleT:
writeln("double işliyorum", veri.d);
break;
}
}
void main()
{}
Ek olarak, D'nin derleme zamanı olanakları çok üstün olduğundan, tam da o veri dosyasını taramayı bilen bir tür derleme zamanında bile oluşturulabilir. O zaman bir DSL (domain specific language) uygulamış olunur.
Sonuçta çözüm çok. :)
Ali
--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]