Çokuzlular
Çokuzlu birden fazla değeri bir araya getirerek hep birden bir yapı nesnesi gibi kullanılmalarını sağlayan olanaktır. Bazı dillerin iç olanağı olan çokuzlular D'de std.typecons
modülündeki Tuple
ile bir kütüphane olanağı olarak gerçekleştirilmiştir.
Tuple
bazı işlemleri için std.meta
modülündeki AliasSeq
'ten da yararlanır.
Tuple
ve tuple()
Çokuzlular Tuple
şablonu ile gerçekleştirilmişlerdir. Çoğunlukla kolaylık işlevi olan tuple()
ile oluşturulurlar:
import std.stdio; import std.typecons; void main() { auto çokuzlu = tuple(42, "merhaba"); writeln(çokuzlu); }
Yukarıdaki tuple()
işlevi 42 değerindeki int
'in ve "merhaba"
değerindeki string
'in bir araya gelmesinden oluşan bir nesne oluşturur. Bu nesnenin türünü ve üyelerinin değerlerini programın çıktısında görüyoruz:
Tuple!(int, string)(42, "merhaba")
O çokuzlu türünün aşağıdaki sözde yapının eşdeğeri olduğunu ve perde arkasında da öyle gerçekleştirildiğini düşünebilirsiniz:
// Tuple!(int, string)'in eşdeğeri struct __Çokuzlu_int_string { int __üye_0; string __üye_1; }
Çokuzluların üyelerine normalde sıra numarasıyla erişilir. Bu açıdan bakıldığında her bir üyesi farklı türden olabilen bir dizi gibi düşünülebilir:
writeln(çokuzlu[0]); writeln(çokuzlu[1]);
Çıktısı:
42 merhaba
Üye isimleri
tuple()
ve Tuple
şablonlarına üye isimleri de bildirildiğinde çokuzlu üyelerine isimlerle de erişilebilir:
auto a = tuple!("sayı", "mesaj")(42, "merhaba"); auto b = Tuple!(int, "sayı", string, "mesaj")(42, "merhaba");
Yukarıdaki tanımlar, int
türündeki 0 numaralı üyeye ayrıca .sayı
niteliğiyle ve string
türündeki 1 numaralı üyeye ayrıca .mesaj
niteliğiyle erişilmesini sağlar:
writeln("0 sıra numarasıyla : ", a[0]); writeln(".sayı niteliği olarak : ", a.sayı); writeln("1 sıra numarasıyla : ", a[1]); writeln(".mesaj niteliği olarak: ", a.mesaj);
Çıktısı:
0 sıra numarasıyla : 42 .sayı niteliği olarak : 42 1 sıra numarasıyla : merhaba .mesaj niteliği olarak: merhaba
Üyelerin değer listesi olarak açılmaları
Çokuzlu nesneleri üyelerinin değerlerinden oluşan liste olarak açılabilir ve örneğin o türlere uyan bir işlevi çağırırken kullanılabilir. Bu, .expand
niteliği ile veya çokuzlu nesnesi dilimlenerek sağlanır:
import std.stdio; import std.typecons; void foo(int i, string s, double d, char c) { // ... } void bar(int i, double d, char c) { // ... } void main() { auto ç = tuple(1, "2", 3.3, '4'); // İkisi de foo(1, "2", 3.3, '4')'ün eşdeğeridir: foo(ç.expand); foo(ç[]); // bar(1, 3.3, '4')'ün eşdeğeridir: bar(ç[0], ç[$-2..$]); }
Yukarıdaki çokuzlu int
, string
, double
, ve char
türündeki değerlerden oluşmaktadır. Bu yüzden, bütün üyelerinin açılmasından oluşan liste foo()
'nun parametre listesine uyar ve o yüzden foo()
çağrılırken kullanılabilir. bar()
çağrılırken ise yalnızca ilk üyesinin ve son iki üyesinin değerlerinden oluşan üç değer gönderilmektedir.
Üyelerin türleri aynı dizinin elemanı olabilecek kadar uyumlu olduklarında çokuzlunun açılımı bir diziyi ilklerken de kullanılabilir:
import std.stdio; import std.typecons; void main() { auto çokuzlu = tuple(1, 2, 3); auto dizi = [ çokuzlu.expand, çokuzlu[] ]; writeln(dizi); }
Yukarıdaki örnek dizi üç int
'ten oluşan çokuzlunun iki kere açılmasından oluşmaktadır:
[1, 2, 3, 1, 2, 3]
Derleme zamanı foreach
'i
Hem dizi gibi düşünülebildiklerinden hem de değerleri liste olarak açılabildiğinden çokuzlular foreach
ile de kullanılabilirler:
auto çokuzlu = tuple(42, "merhaba", 1.5); foreach (i, üye; çokuzlu) { writefln("%s: %s", i, üye); }
Çıktısı:
0: 42 1: merhaba 2: 1.5
Yukarıdaki koddaki foreach
'in çalışma zamanında işletildiği düşünülebilir; ancak, bu doğru değildir. Çokuzlu üyeleri üzerinde işletilen foreach
'ler aslında döngü değil, döngünün içeriğinin üye adedi kadar tekrarlanmasından oluşan bir döngü açılımıdır. Dolayısıyla, yukarıdaki foreach
döngüsü aşağıdaki üç kod bloğunun eşdeğeridir:
{ enum size_t i = 0; int üye = çokuzlu[i]; writefln("%s: %s", i, üye); } { enum size_t i = 1; string üye = çokuzlu[i]; writefln("%s: %s", i, üye); } { enum size_t i = 2; double üye = çokuzlu[i]; writefln("%s: %s", i, üye); }
Bunun nedeni, her çokuzlu üyesinin farklı türden olabilmesi ve dolayısıyla döngünün her ilerletilişinde döngü kapsamındaki kodların farklı olarak derlenmesinin gerekmesidir.
Aynı amaçla kullanılan ama bazı durumlarda daha kullanışlı olabilen static foreach
olanağını sonraki bir bölümde göreceğiz.
Birden fazla değer döndürmek
Çokuzlular işlevlerin tek değer döndürebilme yetersizliklerine karşı basit bir çözüm olarak görülebilirler. Örneğin, std.algorithm
modülündeki findSplit()
bir aralığı başka bir aralık içinde arar ve arama sonucunda üç bilgi üretir: bulunan aralıktan öncesi, bulunan aralık, ve bulunan aralıktan sonrası. findSplit()
, bu üç parça bilgiyi bir çokuzlu olarak döndürür:
import std.algorithm; // ... auto bütünAralık = "merhaba"; auto aranan = "er"; auto sonuç = findSplit(bütünAralık, aranan); writeln("öncesi : ", sonuç[0]); writeln("bulunan: ", sonuç[1]); writeln("sonrası: ", sonuç[2]);
Çıktısı:
öncesi : m bulunan: er sonrası: haba
Birden fazla değer için yapı nesnesi de döndürülebilir:
struct Sonuç { // ... } Sonuç işlev() { // ... }
AliasSeq
std.meta
modülünde tanımlı olan AliasSeq
normalde derleyiciye ait olan ve hep üstü kapalı olarak geçen ve yukarıda da rastladığımız bir kavramı programcının kullanımına sunar: virgüllerle ayrılmış değer listesi. Aşağıda bunun üç örneğini görmekteyiz:
- İşlev parametre değeri listesi
- Şablon parametre değeri listesi
- Dizi hazır değeri eleman listesi
Bu üç farklı listenin örnekleri şöyle gösterilebilir:
işlev(1, "merhaba", 2.5); // işlev parametreleri auto n = YapıŞablonu!(char, long)(); // şablon parametreleri auto d = [ 1, 2, 3, 4 ]; // dizi elemanları
Daha yukarıda örneklerini gördüğümüz Tuple
üyelerinin değer listesi olarak açılabilmeleri de aslında AliasSeq
tarafından sağlanır.
AliasSeq
'in adı "sıralanmış isimler" olarak açıklanabilen "alias sequence"tan gelir ve türler, değerler, ve isimler içerir. (AliasSeq
ve std.meta
'nın eski isimleri sırasıyla TypeTuple
ve std.typetuple
idi.)
Bu bölümde AliasSeq
'in yalnızca ya bütünüyle türlerden ya da bütünüyle değerlerden oluşan örneklerini göreceğiz. Hem türlerden hem değerlerden oluşan örneklerini bir sonraki bölüme ayıracağız. AliasSeq
bir sonraki bölümde göreceğimiz belirsiz sayıda parametre alan şablonlarda da yararlıdır.
Değerlerden oluşan AliasSeq
Bir derleme zamanı olanağı olan AliasSeq
, ifade ettiği parametre listesini kendi şablon parametreleri olarak alır. Bunu üç parametre alan bir işlev çağrısında görelim:
import std.stdio; void foo(int i, string s, double d) { writefln("foo çağrıldı: %s %s %s", i, s, d); }
Normalde o işlevin açıkça üç parametre değeri ile çağrıldığını biliyoruz:
foo(1, "merhaba", 2.5);
AliasSeq
o parametre değerlerini tek değişken olarak bir arada tutabilir ve işlev çağrıları sırasında otomatik olarak parametre listesi olarak açılabilir:
import std.meta; // ... alias parametreler = AliasSeq!(1, "merhaba", 2.5); foo(parametreler);
Her ne kadar bu sefer tek değer alıyormuş gibi görünse de, yukarıdaki foo
çağrısı öncekinin eşdeğeridir ve her iki yöntem de aynı çıktıyı üretir:
foo çağrıldı: 1 merhaba 2.5
parametreler
'in auto
anahtar sözcüğü ile değişken olarak değil, alias
sözcüğü ile belirli bir AliasSeq
'in takma ismi olarak tanımlandığına dikkat edin. auto
anahtar sözcüğünün kullanılabildiği durumlar olsa da bu bölümdeki örneklerde yalnızca takma isim olarak göreceğiz.
Yukarıda Tuple
başlığı altında da gördüğümüz gibi, değerlerin hepsi aynı türden veya daha genel olarak uygun türlerden olduklarında, AliasSeq
bir dizi hazır değerinin elemanlarını da temsil edebilir:
alias elemanlar = AliasSeq!(1, 2, 3, 4); auto dizi = [ elemanlar ]; assert(dizi == [ 1, 2, 3, 4 ]);
Türlerden oluşan AliasSeq
AliasSeq
'in parametreleri türlerin kendilerinden de oluşabilir. Yani, belirli bir türün belirli bir değeri değil, int
gibi bir türün kendisi olabilir.
Tür içeren AliasSeq
'ler şablonlarla kullanılmaya elverişlidir. Bunun bir örneğini görmek için iki parametreli bir yapı şablonu düşünelim. Bu şablonun ilk parametresi yapının bir dizisinin eleman türünü, ikincisi de yapının bir işlevinin dönüş türünü belirliyor olsun:
import std.conv; struct S(ElemanTürü, SonuçTürü) { ElemanTürü[] dizi; SonuçTürü uzunluk() { return to!SonuçTürü(dizi.length); } } void main() { auto s = S!(double, int)([ 1, 2, 3 ]); auto u = s.uzunluk(); }
Yukarıda bu şablonun (double, int)
türleri ile kullanıldığını görüyoruz. Aynı amaç için iki tür içeren bir AliasSeq
'ten de yararlanılabilir:
import std.meta; // ... alias Türler = AliasSeq!(double, int); auto s = S!Türler([ 1, 2, 3 ]);
Yukarıda S
şablonu her ne kadar tek şablon parametresi ile kullanılıyor gibi görünse de, Türler
otomatik olarak açılır ve sonuçta S!(double, int)
türünün aynısı elde edilir.
AliasSeq
özellikle belirsiz sayıda parametre alan şablonlarda yararlıdır. Bunun örneklerini bir sonraki bölümde göreceğiz.
Dizi gibi kullanılması
AliasSeq
'in kurulduğu şablon parametrelerine dizi erişim işleci ile erişilebilir:
alias parametreler = AliasSeq!(1, "merhaba", 2.5); assert(parametreler[0] == 1); assert(parametreler[1] == "merhaba"); assert(parametreler[2] == 2.5);
AliasSeq
yine dizilerde olduğu gibi dilimleme işleminde de kullanılabilir. Yukarıdaki örneklerde kullanılan AliasSeq
'in son iki parametresine uyan bir işlev olduğunu düşünelim. Böyle bir işlev yukarıdaki parametreler
'in son iki değeri dilimlenerek çağrılabilir:
void bar(string s, double d) { // ... } // ... bar(parametreler[$-2 .. $]);
foreach
ile kullanılması
Yukarıda gördüğümüz Tuple
'da olduğu gibi, AliasSeq
'in foreach
ile kullanılmasında da çalışma zamanında işletilen bir döngü oluşmaz; döngünün içeriği parametre listesindeki her eleman için derleme zamanında kod olarak açılır ve sonuçta o açılım derlenir.
Bunun örneğini yukarıdaki S
yapı şablonu için yazılmış olan bir birim testinde görelim. Aşağıdaki kod bu yapı şablonunun eleman türü olarak int
, long
, ve float
kullanılabildiğini test ediyor (SonuçTürü
ise hep size_t
):
unittest { alias Türler = AliasSeq!(int, long, float); foreach (Tür; Türler) { auto s = S!(Tür, size_t)([ Tür.init, Tür.init ]); assert(s.uzunluk() == 2); } }
Yukarıdaki koddaki Tür
değişkeni sırasıyla int
, long
, ve float
türünü temsil eder ve sonuçta foreach
döngüsü aşağıdaki eşdeğer döngü açılımı olarak derlenir:
{ auto s = S!(int, size_t)([ int.init, int.init ]); assert(s.uzunluk() == 2); } { auto s = S!(long, size_t)([ long.init, long.init ]); assert(s.uzunluk() == 2); } { auto s = S!(float, size_t)([ float.init, float.init ]); assert(s.uzunluk() == 2); }
.tupleof
niteliği
.tupleof
bir türün veya bir nesnenin bütün üyelerini bir çokuzlu olarak elde etmeye yarar. Aşağıdaki örnek .tupleof
niteliğini bir türe uyguluyor:
import std.stdio; struct Yapı { int numara; string dizgi; double kesirli; } void main() { foreach (i, ÜyeTürü; typeof(Yapı.tupleof)) { writefln("Üye %s:", i); writefln(" tür : %s", ÜyeTürü.stringof); string isim = Yapı.tupleof[i].stringof; writefln(" isim: %s", isim); } }
Yapı.tupleof
'un yukarıda iki yerde geçtiğine dikkat edin. İlkinde eleman türleri typeof
ile elde edilmekte ve her tür foreach
'in ÜyeTürü
değişkeni olarak belirmektedir. İkincisinde ise yapı üyesinin ismi Yapı.tupleof[i].stringof
ile elde edilmektedir.
Üye 0: tür : int isim: numara Üye 1: tür : string isim: dizgi Üye 2: tür : double isim: kesirli
.tupleof
bir nesneye uygulandığında ise o nesnenin üyelerinin değerlerini çokuzlu olarak elde etmeye yarar:
auto nesne = Yapı(42, "merhaba", 1.5); foreach (i, üye; nesne.tupleof) { writefln("Üye %s:", i); writefln(" tür : %s", typeof(üye).stringof); writefln(" değer: %s", üye); }
Bu durumda üye
isimli döngü değişkeni sırasıyla üyelerin değerlerini temsil eder:
Üye 0: tür : int değer: 42 Üye 1: tür : string değer: merhaba Üye 2: tür : double değer: 1.5
Buradaki önemli bir ayrıntı, nesneye uygulanan .tupleof
çokuzlusunun üyelerin değerlerinden değil, üyelerin kendilerinden oluşmasıdır. Bir anlamda, her çokuzlu üyesi temsil ettiği asıl üyenin referansıdır.
Özet
tuple()
veTuple
yapı benzeri çokuzlular oluşturur.- Çokuzlu üyelerine isimle de erişilebilir.
- Üyeler
.expand
niteliği ile veya dilimlenerek değer listesi olarak açılabilirler. - Çokuzluya uygulanan
foreach
döngü değildir, döngü açılımıdır. AliasSeq
parametre değer listesi gibi kavramları temsil eder.AliasSeq
hem değerleri hem türlerin kendilerini içerebilir.Tuple
veAliasSeq
dizi erişim ve dilimleme işleçlerini destekler..tupleof
türlerin veya nesnelerin üyeleri ile ilgili bilgi verir.