D Programlama Dili – Programlama dersleri ve D referansı
Ali Çehreli

değer: [value], ay adedi 12 gibi isimsiz bir büyüklük
değişken: [variable], kavramları temsil eden veya sınıf nesnesine erişim sağlayan program yapısı
değişmez: [immutable], programın çalışması süresince kesinlikle değişmeyen
deyim: [statement], ifadelerin işletilmelerini ve sıralarını etkileyen program yapısı
dizgi: [string], "merhaba" gibi bir karakter dizisi
dönüş değeri: [return value], işlevin üreterek döndürdüğü değer
ifade: [expression], programın değer oluşturan veya yan etki üreten bir bölümü
işlev: [function], programdaki bir kaç adımı bir araya getiren program parçası
nesne: [object], belirli bir sınıf veya yapı türünden olan değişken
parametre: [parameter], işleve işini yapması için verilen bilgi
parametre değeri: [argument], işleve parametre olarak verilen bir değer
yan etki: [side effect], bir ifadenin, ürettiği değer dışındaki etkisi
... bütün sözlük



İngilizce Kaynaklar


Diğer




İşlevler

İşlevler gerçek programların temel taşlarıdır. Nasıl temel türler, bütün türlerin yapı taşları iseler, işlevler de program davranışlarının yapı taşlarıdır.

İşlevlerin ustalıkla da ilgisi vardır. Usta programcıların yazdıkları işlevler kısa ve öz olur. Bunun tersi de doğrudur: kısa ve öz işlevler yazmaya çalışmak, ustalık yolunda ilerlemenin önemli adımlarındandır. İşlemleri oluşturan alt adımları görmeye çalışmak ve o adımları küçük işlevler halinde yazmak, programcılık konusunda gelişmenize yardım edecektir.

Bundan önceki bölümlerde temel deyimler ve ifadeler öğrendik. Daha hepsini bitirmedik ama D'nin programlarda çok kullanılan, çok yararlı, ve çok önemli olanaklarını gördük. Yine de, hiçbirisi büyük programlar yazmak için yeterli değildir. Şimdiye kadar yazdığımız biçimdeki programlar, deneme programları gibi hiçbir karmaşıklığı olmayan çok basit programlar olabilirler. En ufak bir karmaşıklığı bulunan bir işi işlev kullanmadan yazmaya çalışmak çok zordur, ve ortaya çıkan program da hataya açık olur.

İşlevler, ifade ve deyimleri bir araya getiren olanaklardır. Bir araya getirilen ifade ve deyimlere toplu olarak yeni bir isim verilir ve o işlemlerin hepsi birden bu isimle işletilir.

Bir araya getirerek yeni isim verme kavramını günlük hayattan tanıyoruz. Örneğin yağda yumurta yapma işini şu adımlarla tarif edebiliriz:

O kadar ayrıntıya girmek zamanla gereksiz ve içinden çıkılmaz bir hâl alacağı için, birbiriyle ilişkili adımların bazılarına tek bir isim verilebilir:

Daha sonra daha da ileri gidilebilir ve bütün o adımları içeren tek bir ifade de kullanılabilir:

İşlevlerin bundan farkı yoktur: Yaptıkları işlerin hepsine birden genel bir isim verilebilen adımlar tek bir işlev olarak tanımlanırlar. Örnek olarak kullanıcıya bir menü gösteren şu satırlara bakalım:

    writeln(" 0 Çıkış");
    writeln(" 1 Toplama");
    writeln(" 2 Çıkarma");
    writeln(" 3 Çarpma");
    writeln(" 4 Bölme");

Onların hepsine birden menüyüGöster gibi bir isim verilebileceği için onları bir işlev olarak şu şekilde bir araya getirebiliriz:

void menüyüGöster() {
    writeln(" 0 Çıkış");
    writeln(" 1 Toplama");
    writeln(" 2 Çıkarma");
    writeln(" 3 Çarpma");
    writeln(" 4 Bölme");
}

Artık o işlevi main içinden kısaca ismiyle işletebiliriz:

import std.stdio;

void main() {
    menüyüGöster();

    // ... diğer işlemler ...
}

menüyüGöster ile main'in tanımlarındaki benzerliğe bakarak main'in de bir işlev olduğunu görebilirsiniz. İsmi İngilizce'de "ana işlev" kullanımındaki "ana" anlamına gelen main, D programlarının ana işlevidir. D programlarının işleyişi bu işlevle başlar ve programcının istediği şekilde başka işlevlere dallanır.

Parametreler

İşlevlerin güçlü yanlarından birisi, yaptıkları işlerin belirli ölçüde ayarlanabiliyor olmasından gelir.

Yine yumurta örneğine dönelim, ve bu sefer beş yumurta yapmak isteyelim. İzlenmesi gereken adımlar aslında bu durumda da aynıdır; tek farkları, yumurta sayısındadır. Daha önce dörde indirgediğimiz adımları beş yumurtaya uygun olarak şöyle değiştirebiliriz:

Teke indirgediğimiz adım da şöyle değişir:

Temelde aynı olan yumurta pişirme işiyle ilgili bir bilgi ek olarak belirtilmektedir: "beş yumurta çıkart" veya "beş yumurta yap" gibi.

İşlevlerin davranışları da benzer şekilde ayarlanabilir. İşlevlerin işlerini bu şekilde etkileyen bilgilere parametre denir. Parametreler, parametre listesinde virgüllerle ayrılarak bildirilirler. Parametre listesi, işlevin isminden hemen sonra yazılan parantezin içidir.

Daha önceki menüyüGöster işlevinde parametre parantezini boş olarak tanımlamıştık çünkü o işlev her zaman için aynı menüyü göstermekteydi. Menüdeki ilk seçeneğin hep "Çıkış" olması yerine, duruma göre değişen bir seçenek olmasını istesek, bunu bir parametreyle sağlayabiliriz. Örneğin ilk seçeneği bazı durumlarda "Geri Dön" olarak yazdırmak için bu bilgiyi bir parametre olarak tanımlayabiliriz:

void menüyüGöster(string ilkSeçenek) {
    writeln(" 0 ", ilkSeçenek);
    writeln(" 1 Toplama");
    writeln(" 2 Çıkarma");
    writeln(" 3 Çarpma");
    writeln(" 4 Bölme");
}

ilkSeçenek parametresi bu örnekte bir dizgi olduğu için türünü de string olarak belirledik. Bu işlevi artık değişik dizgilerle işleterek menünün ilk satırının farklı olmasını sağlayabiliriz. Tek yapmamız gereken, parametre değerini parantez içinde belirtmektir:

    menüyüGöster("Çıkış");
    menüyüGöster("Geri Dön");

Not: Burada parametrenin türüyle ilgili bir sorunla karşılaşabilirsiniz: Bu işlev yukarıda yazıldığı haliyle char[] türünde dizgilerle kullanılamaz. Örneğin, char[] ve string uyumlu olmadıklarından aşağıdaki kod derleme hatasına neden olur:

    char[] birSeçenek;
    birSeçenek ~= "Kare Kök Al";
    menüyüGöster(birSeçenek);   // ← derleme HATASI

Öte yandan, menüyüGöster'in tanımında parametrenin türünü char[] olarak belirlediğinizde de işlevi "Çıkış" gibi bir string değeriyle çağıramazsınız. immutable ile ilgili olan bu konuyu bir sonraki bölümde göreceğiz.

Biraz daha ileri gidelim ve seçenek numaralarının hep 0 ile değil, duruma göre değişik bir değerle başlamasını istiyor olalım. Bu durumda başlangıç numarasını da parametre olarak verebiliriz. Parametreler virgüllerle ayrılırlar:

void menüyüGöster(string ilkSeçenek, int ilkNumara) {
    writeln(' ', ilkNumara, ' ', ilkSeçenek);
    writeln(' ', ilkNumara + 1, " Toplama");
    writeln(' ', ilkNumara + 2, " Çıkarma");
    writeln(' ', ilkNumara + 3, " Çarpma");
    writeln(' ', ilkNumara + 4, " Bölme");
}

O işleve hangi numarayla başlayacağını artık biz bildirebiliriz:

    menüyüGöster("Geri Dön", 1);
İşlev çağırmak

İşlevin işini yapması için başlatılmasına işlevin çağrılması denir. İşlev çağrısının söz dizimi şöyledir:

    işlevin_ismi(parametre_değerleri)

İşini yaparken kullanması için işleve verilen bilgilere parametre değeri denir. Parametre değerleri işlevin tanımındaki parametrelerle bire bir eşleşirler. Örneğin, yukarıdaki menüyüGöster() işlev çağrısındaki "Geri Dön" ve 1 değerleri sırayla ilkSeçenek ve ilkNumara parametrelerine karşılık gelirler.

Her parametre değerinin türü, karşılık geldiği parametrenin türüne uymalıdır.

İş yapmak

Hem daha önceki bölümlerde hem de bu bölümde iş yapmaktan söz ettim. Program adımlarının, ifadelerin, işlevlerin, iş yaptıklarını söyledim. İş yapmak, değer üretmek veya yan etki oluşturmak anlamına gelir:

İşlevin dönüş değeri

Değer üreten bir işlevin ürettiği değere o işlevin dönüş değeri denir. Bu terim, işlevin işini bitirdikten sonra bize geri dönmesi gibi bir düşünceden türemiştir. İşlevi "çağırırız" ve "döndürdüğü" değeri kullanırız.

Her değerin olduğu gibi, dönüş değerinin de türü vardır. Bu tür işlevin isminden önce yazılır. Örneğin iki tane int değeri toplayan bir işlev, eğer sonuçta yine int türünde bir değer üretiyorsa, dönüş türü olarak int yazılır:

int topla(int birinci, int ikinci) {
    // ...  yapılan işlemler ...
}

İşlevin döndürdüğü değer, sanki o değer işlev çağrısının yerine yazılmış gibi, onun yerine geçer. Örneğin topla(5, 7) çağrısının değer olarak 12 ürettiğini düşünürsek, şu iki satır birbirinin eşdeğeridir:

    writeln("Toplam: ", topla(5, 7));
    writeln("Toplam: ", 12);

writeln çağrılmadan önce, topla(5, 7) işlevi çağrılır ve onun döndürdüğü değer olan 12, yazdırması için writeln'e parametre olarak verilir.

Bu sayede işlevlerin değerlerini başka işlevlere parametre olarak verebilir ve daha karmaşık ifadeler oluşturabiliriz:

    writeln("Sonuç: ", topla(5, böl(100, kişiSayısı())));

O örnekte kişiSayısı'nın dönüş değeri böl'e, böl'ün dönüş değeri topla'ya, ve en sonunda da topla'nın dönüş değeri writeln'e parametre olarak verilmektedir.

return deyimi

İşlevin ürettiği değer, "döndür" anlamına gelen return anahtar sözcüğü ile bildirilir:

int topla(int birinci, int ikinci) {
    int toplam = birinci + ikinci;
    return toplam;
}

İşlev, gereken işlemleri ve hesapları yaparak dönüş değerini üretir ve en son olarak return ile döndürür. İşlevin işleyişi de o noktada sona erer ve işlevden dönülmüş olur.

İşlevlerde birden fazla return anahtar sözcüğü kullanılabilir. İfade ve deyimlere bağlı olarak önce hangi return işletilirse, işlevin dönüş değeri olarak o return'ün döndürdüğü değer kullanılır:

int karmaşıkHesap(int birParametre, int başkaParametre) {
    if (birParametre == başkaParametre) {
        return 0;
    }

    return birParametre * başkaParametre;
}

O işlev; iki parametresi birbirlerine eşitse 0 değerini, değilse iki parametrenin çarpımını döndürür.

void işlevler

Eğer işlev değer üretmeyen bir işlevse, dönüş türü olarak "boşluk, yokluk" anlamına gelen void yazılır. O yüzden main'in ve menüyüGöster'in dönüş türlerini void olarak yazdık; şimdiye kadar gördüğümüz kadarıyla ikisi de değer üretmeyen işlevlerdir.

Not: main aslında int de döndürebilir. Bunu sonraki bir bölümde göreceğiz.

İşlevin ismi

İşlevin ismi, programcı tarafından işlevin yaptığı işi açıklayacak şekilde seçilmelidir. Örneğin iki sayıyı toplayan işlevin ismini topla olarak seçtik; veya menüyü gösteren işleve menüyüGöster dedik.

İşlevlere isim verirken izlenen bir kural, isimleri topla'da ve menüyüGöster'de olduğu gibi ikinci tekil şahıs emir kipinde seçmektir. Yani toplam'da ve menü'de olduğu gibi isim halinde değil. Böylece işlevin bir eylemde bulunduğu isminden anlaşılır.

Öte yandan hiçbir yan etkileri olmayan, yani yalnızca değer üreten işlevlere içinde eylem bulunmayan isimler de seçilebilir. Örneğin şu andaki hava sıcaklığını veren bir işlev için havaSıcaklığınıVer yerine havaSıcaklığı gibi bir ismin daha uygun olduğunu düşünebilirsiniz.

İşlev, nesne, değişken, vs. isim seçimlerinin programcılığın sanat tarafında kaldığını düşünebilirsiniz. İşe yarar, yeterince kısa, ve programdaki diğer isimlerle tutarlı olan isimler bulmak bazen yaratıcılık gerektirir.

İşlevlerin kod kalitesine etkileri

İşlevlerin kod kalitesine etkileri büyüktür. İşlevlerin küçük olmaları ve sorumluluklarının az olması programların bakımlarını kolaylaştırır.

Programı bir bütün halinde main içinde yazmak yerine küçük parçalara ayırmak bütün programı kolaylaştırır. Küçük işlevlerin birim olarak işlemleri de basit olacağından, teker teker yazılmaları çok daha kolay olur. Programın diğer işlemleri bu yapı taşları üzerine kurulunca bütün program daha kolay yazılır. Daha da önemlisi, programda daha sonradan gereken değişiklikler de çok daha kolay hale gelirler.

Kod tekrarından kaçının

Programcılıkta kaçınılması gereken bir eylem, kod tekrarıdır. Kod tekrarı, aynı işi yapan işlemlerin programda birden fazla yerde tekrarlanması anlamına gelir.

Bu tekrar bazen bilinçli olarak satırların bir yerden başka bir yere kopyalanması ile yapılabilir. Bazen de farkında olmadan, aynı işlemlerin aynı şekilde kodlanmaları şeklinde ortaya çıkabilir.

Kod tekrarının sakıncalarından birisi; tekrarlanan işlemlerdeki olası hataların bütün kopyalarda da bulunması, ve aynı hatanın her kopyada giderilmesinin gerekmesidir. Oysa; tekrarlanan kod tek bir işlev içinde bulunuyor olsa, hatayı yalnızca bir kere gidermek yeter.

Yukarıda işlevlerin ustalıkla ilgili olduklarına değinmiştim. Usta programcılar koddaki işlemler arasındaki benzerlikleri yakalamaya ve kod tekrarını ortadan kaldırmaya çalışırlar.

Bir örnek olarak, girişten aldığı sayıları önce girişten geldikleri sırada, sonra da sıralanmış olarak yazdıran şu programa bakalım:

import std.stdio;
import std.algorithm;

void main() {
    int[] sayılar;

    int adet;
    write("Kaç sayı gireceksiniz? ");
    readf(" %s", &adet);

    // Sayıları oku
    foreach (i; 0 .. adet) {
        int sayı;
        write("Sayı ", i, "? ");
        readf(" %s", &sayı);

        sayılar ~= sayı;
    }

    // Diziyi çıkışa yazdır
    writeln("Sıralamadan önce:");
    foreach (i, sayı; sayılar) {
        writefln("%3d:%5d", i, sayı);
    }

    sort(sayılar);

    // Diziyi çıkışa yazdır
    writeln("Sıraladıktan sonra:");
    foreach (i, sayı; sayılar) {
        writefln("%3d:%5d", i, sayı);
    }
}

Kod tekrarını görüyor musunuz? Diziyi yazdırmak için kullanılan son iki foreach döngüsü birbirinin aynısı. yazdır ismiyle bir işlev tanımlasak ve yazdırmasını istediğimiz diziyi de parametre olarak versek, bu kod tekrarını ortadan kaldırmış oluruz:

void yazdır(int[] dizi) {
    foreach (i, eleman; dizi) {
        writefln("%3s:%5s", i, eleman);
    }
}

Dikkat ederseniz, parametrenin ismi olarak sayılar yerine ondan daha genel olan dizi ismini seçtik. Bunun nedeni, bu işlev bağlamında dizi yazdırmak dışında bir şey bilmiyor olduğumuzdur. Dizinin elemanlarının ne olduklarından bu işlev içinde haberimiz yoktur. Dizideki int elemanların ne anlama geldiklerini ancak bu işlevi çağıran kapsam bilir: belki öğrenci kayıt numaralarıdır, belki bir şifrenin parçalarıdır, belki bir grup insanın yaşlarıdır... Ne olduklarını yazdır işlevi içinde bilemediğimiz için, ancak dizi ve eleman gibi genel isimler kullanabiliyoruz.

Şimdi kod biraz daha düzenli bir hale gelir:

import std.stdio;
import std.algorithm;

void yazdır(int[] dizi) {
    foreach (i, eleman; dizi) {
        writefln("%3s:%5s", i, eleman);
    }
}

void main() {
    int[] sayılar;

    int adet;
    write("Kaç sayı gireceksiniz? ");
    readf(" %s", &adet);

    // Sayıları oku
    foreach (i; 0 .. adet) {
        int sayı;
        write("Sayı ", i, "? ");
        readf(" %s", &sayı);

        sayılar ~= sayı;
    }

    // Diziyi çıkışa yazdır
    writeln("Sıralamadan önce:");
    yazdır(sayılar);

    sort(sayılar);

    // Diziyi çıkışa yazdır
    writeln("Sıraladıktan sonra:");
    yazdır(sayılar);
}

İşimiz bitmedi: iki yazdır çağrısından önce birer de başlık yazdırılıyor. Yazdırılan dizgi farklı olsa da işlem aynıdır. Eğer başlığı da yazdır'a parametre olarak verirsek, başlığı yazdırma tekrarından da kurtulmuş oluruz. Programın yalnızca değişen bölümlerini gösteriyorum:

void yazdır(string başlık, int[] dizi) {
    writeln(başlık, ":");

    foreach (i, eleman; dizi) {
        writefln("%3s:%5s", i, eleman);
    }
}

// ...

    // Diziyi çıkışa yazdır
    yazdır("Sıralamadan önce", sayılar);

// ...

    // Diziyi çıkışa yazdır
    yazdır("Sıraladıktan sonra", sayılar);

Bu işlemin yazdır'dan önceki açıklama satırlarını da gereksiz hale getirdiğini görebiliriz. İşlemlere yazdır diye açıklayıcı bir isim verdiğimiz için, ayrıca "Diziyi çıkışa yazdır" gibi bir açıklamaya da gerek kalmamış oluyor. Şimdi programın son satırları şöyle kısaltılabilir:

    yazdır("Sıralamadan önce", sayılar);
    sort(sayılar);
    yazdır("Sıraladıktan sonra", sayılar);

Bu programda bir kod tekrarı daha var: girişten adet ve sayı için aynı şekilde tamsayı okunuyor. Tek farkları, kullanıcıya gösterilen mesaj ve değişkenin ismi:

    int adet;
    write("Kaç sayı gireceksiniz? ");
    readf(" %s", &adet);

// ...

        int sayı;
        write("Sayı ", i, "? ");
        readf(" %s", &sayı);

sayıOku diye bir işlev yazarsak ve kullanıcıya gösterilecek mesajı bir parametre olarak alırsak, kod çok daha temiz bir hale gelir. Bu sefer bu işlevin girişten okuduğu değeri döndürmesi gerektiğine dikkat edin:

int sayıOku(string mesaj) {
    int sayı;
    write(mesaj, "? ");
    readf(" %s", &sayı);
    return sayı;
}

Bu işlevi çağırarak adet'in değerini okumak kolay. adet'i işlevin dönüş değeriyle ilkleyebiliriz:

    int adet = sayıOku("Kaç sayı gireceksiniz");

sayı'yı okurken kullanılan mesaj döngü sayacı olan i'yi de içerdiğinden o mesaj std.string modülündeki format'tan yararlanılarak her i için farklı olarak oluşturulabilir:

import std.string;
// ...
        int sayı = sayıOku(format("Sayı %s", i));

sayı'nın foreach içinde tek bir yerde kullanıldığını görerek sayı'nın tanımını da tamamen kaldırabilir ve onun kullanıldığı tek yerde doğrudan sayıOku işlevini çağırabiliriz. Böylece döngü içindeki satırlar da azalmış olur:

    foreach (i; 0 .. adet) {
        sayılar ~= sayıOku(format("Sayı %s", i));
    }

Ben bu programda son bir değişiklik daha yapacağım ve sayıların okunmasıyla ilgili bütün işlemleri tek bir işleve taşıyacağım. Böylece "Sayıları oku" açıklaması da ortadan kalkacak; çünkü yeni işlevin ismi zaten ne işlem yapılmakta olduğunu açıklayacak.

sayılarıOku ismini verebileceğimiz bu işlevin hiçbir parametre alması gerekmez, ama değer olarak bütün diziyi üretirse ismi ile de uyumlu bir kullanımı olur.

Böylece bütün program son olarak şöyle yazılabilir:

import std.stdio;
import std.string;
import std.algorithm;

void yazdır(string başlık, int[] dizi) {
    writeln(başlık, ":");

    foreach (i, eleman; dizi) {
        writefln("%3s:%5s", i, eleman);
    }
}

int sayıOku(string mesaj) {
    int sayı;
    write(mesaj, "? ");
    readf(" %s", &sayı);
    return sayı;
}

int[] sayılarıOku() {
    int[] sayılar;

    int adet = sayıOku("Kaç sayı gireceksiniz");

    foreach (i; 0 .. adet) {
        sayılar ~= sayıOku(format("Sayı %s", i));
    }

    return sayılar;
}

void main() {
    int[] sayılar = sayılarıOku();
    yazdır("Sıralamadan önce", sayılar);
    sort(sayılar);
    yazdır("Sıraladıktan sonra", sayılar);
}

Programın bu halini ilk haliyle karşılaştırın. Yeni programda main işlevi içinde programın ana adımları açık bir şekilde anlaşılmaktadır. Oysa ilk halinde programın ne yaptığını ancak kodları ve açıklamaları okuyarak anlamak zorunda kalıyorduk.

Programın son halinde daha fazla satır bulunuyor olması sizi yanıltmasın. İşlevler aslında kodu küçültürler, ama bunu çok kısa olan bu programda göremiyoruz. Örneğin sayıOku işlevini yazmadan önce girişten tamsayı okumak için her seferinde 3 satır kod yazıyorduk. Şimdi ise sayıOku'yu çağırarak her noktadaki kod satırı sayısını 1'e indirmiş olduk. Hatta, foreach döngüsü içindeki sayı'nın tanımını da tamamen kaldırabildik.

Açıklamalı kod satırlarını işlevlere dönüştürün

Eğer programdaki işlemlerin bazılarının ne yaptıklarını açıklama satırları yazarak açıklama gereği duyuyorsanız, belki de o işlemlerin bir işleve taşınmaları zamanı gelmiştir. İşlevin ismini açıklayıcı olarak seçmek, açıklama satırının gereğini de ortadan kaldırır.

Yukarıdaki programdaki üç açıklama satırından bu sayede kurtulmuş olduk.

Açıklama satırlarından kurtulmanın önemli başka bir nedeni daha vardır: açıklama satırları zaman içinde kodun ne yaptığı hakkında yanlış bilgi vermeye başlarlar. Baştan iyi niyetle ve doğru olarak yazılan açıklama satırı, kod değiştiğinde unutulur ve zamanla koddan ilgisiz hale gelebilir. Artık açıklama satırı ya yanlış bilgi veriyordur, ya da tamamen işe yaramaz durumdadır. Bu yüzden programları açıklama satırlarına gerek bırakmadan yazmaya çalışmak önemlidir.

Problemler
  1. menüyüGöster işlevini bütün seçeneklerini bir dizi olarak alacak şekilde değiştirin. Örneğin şu şekilde çağırabilelim:
        string[] seçenekler =
            [ "Siyah", "Kırmızı", "Yeşil", "Mavi", "Beyaz" ];
        menüyüGöster(seçenekler, 1);
    

    Çıktısı şöyle olsun:

     1 Siyah
     2 Kırmızı
     3 Yeşil
     4 Mavi
     5 Beyaz
    
  2. İki boyutlu bir diziyi bir resim kağıdı gibi kullanan bir program yazdım. Siz bu programı istediğiniz şekilde değiştirin:
    import std.stdio;
    
    enum satırAdedi = 20;
    enum sütunAdedi = 60;
    
    /* alias, "takma isim" anlamına gelir. Programın geri
     * kalanında hep dchar[sütunAdedi] yazmak yerine, daha
     * açıklayıcı olarak 'Satır' yazabilmemizi sağlıyor.
     *
     * Dikkat ederseniz Satır "sabit uzunluklu dizi" türüdür. */
    alias Satır = dchar[sütunAdedi];
    
    /* Bir Satır dilimine de kısaca 'Kağıt' takma ismini
     * veriyoruz. */
    alias Kağıt = Satır[];
    
    /* Verilen kağıdı satır satır ve kare kare çıkışa gönderir. */
    void kağıdıGöster(Kağıt kağıt) {
        foreach (satır; kağıt) {
            writeln(satır);
        }
    }
    
    /* Verilen kağıdın belirtilen yerine bir benek koyar; bir
     * anlamda o kareyi "boyar". */
    void benekKoy(Kağıt kağıt, int satır, int sütun) {
        kağıt[satır][sütun] = '#';
    }
    
    /* Kağıdın belirtilen yerinden aşağıya doğru, belirtilen
     * uzunlukta çizgi çizer. */
    void düşeyÇizgiÇiz(Kağıt kağıt, int satır,
                       int sütun, int uzunluk) {
        foreach (çizilecekSatır; satır .. satır + uzunluk) {
            benekKoy(kağıt, çizilecekSatır, sütun);
        }
    }
    
    void main() {
        Satır boşSatır = '.';
    
        /* Hiç satırı bulunmayan bir kağıt */
        Kağıt kağıt;
    
        /* Ona boş satırlar ekliyoruz */
        foreach (i; 0 .. satırAdedi) {
            kağıt ~= boşSatır;
        }
    
        /* Ve kullanmaya başlıyoruz */
        benekKoy(kağıt, 7, 30);
        düşeyÇizgiÇiz(kağıt, 5, 10, 4);
    
        kağıdıGöster(kağıt);
    }