Published using Google Docs
D ohjelmointikieli
Updated automatically every 5 minutes

D-ohjelmointikieli

1 Johdanto

Tervetuloa tutustumaan D-ohjelmointikieleen. Tämä sivu sisältää kevytspesifikaation kielestä. Sivu on toteutettu osana Helsingin Yliopiston Tietojenkäsittelytieteen laitoksen Ohjelmointikielten periaatteet -kurssia keväällä 2011.

Tekijät

Marko Raatikainen

Jussi Karppinen

1 Johdanto

2 D-ohjelmointikieli yleisesti

2.1 Taustaa

2.2 Toteutustapa

2.3 Esimerkki

3 Alkiorakenne

3.1 Tunnukset

3.2 Varatut sanat

3.3 Literaalit

3.3.1 Merkkijonoliteraalit#

3.3.1.1 Heittomerkkein suljettu merkkijono

3.3.1.2 wysiwygstring

3.3.1.3 hexString

3.3.1.4 Rajatutmerkkijonot

3.3.1.5 TokenString

3.3.1.6 Postfix

3.3.2 Merkkiliteraalit

3.3.3 Numeeriset literaalit

3.3.4 Liukuliteraalit#

3.4 Kommentit

3.5 Arvio ratkaisujen haitoista ja hyödyistä

4 Tunnusten näkyvyysalueet

4.1 Lohkorakenne#

4.2 Sidonta

4.3 Sulkeumat#

4.3.1 Monimutkaisempi esimerkki

4.4 Ensimmäisen, toisen ja kolmannen luokan arvot

4.5 Arvio ratkaisujen eduista ja haitoista

5 Kontrollin ja/tai laskennan ohjaus

5.1 Ajonaikaiset ohjaustavat

5.1.1 && -operaattori lauseessa#

5.1.2 || -operaattori lauseessa#

5.1.3 if-else#

5.1.4 Ehdollinen lauseke#

5.1.5 switch-case#

5.1.6 final switch#

5.1.7 while#

5.1.8 do-while#

5.1.9 for#

5.1.10 foreach numeroarvoille#

5.1.11 foreach tietorakenteilla#

5.1.12 Silmukan keskeytys#

5.1.13 Rekursio

5.1.14 goto#

5.2 Käännöksen aikaiset ohjaustavat

5.2.1 static if#

5.2.2 mixin#

5.3 Valittujen ratkaisujen arviointia

6 Tietotyypit

6.1 Perustietotyypit

6.2 Muut tietotyypit

6.2.1 Taulukot

6.2.2 Tietueet (struct ja union)#

6.2.3 Osoittimet (function ja delegate)

6.2.4 Alias (alias)#

6.2.5 Lueteltu tietotyyppi(enum)

6.3 Tyyppimääreet

7 Toiminnallisuuden kapselointi

7.1 Funktiot#

7.1.1 Ref -funktio

7.1.2 Pure -funktio

7.1.3 Inout funktio

7.1.4 Oletusarvot

7.2 Parametrien välitys#

7.3 Virheiden hallinta

7.3.1 Poikkeukset#

7.3.2 scope#

7.3.3 Ohjelmointivirheet

7.3.4 assert ja static assert#

7.3.5 Sopimuspohjainen ohjelmointi

7.3.5.1 Esi- ja jälkiehdot#

7.3.5.2 Invariantit#

7.3.6 Nopeus vai turvallisuus#

7.4 Tyypillisiä ohjelma-arkkitehtuureja

7.4.1 Imperatiivinen ohjelmointi

7.4.2 Olio-ohjelmointi

7.4.3 Geneerinen ohjelmointi

7.4.4 Funktionaalinen ohjelmointi

7.4.5 Rinnakkainen ohjelmointi

7.5 Rinnakkaisuuden tuki#

7.6 Valittujen ratkaisujen arviointia

8 Datan kapselointi

8.1 Pistetäänkö pakettiin?

8.1.1 Moduulien käyttö toisista moduuleista#

8.1.1.1 public import#

8.1.2 Moduuli-yhteenvedot#

8.2 Luokat

8.2.1 Varsinaiset luokat

8.2.1.1 Konstruktori# ja destruktori#

8.2.1.2 Scope luokka#

8.2.2 Tietueet

8.2.2.1 Tietueen luonti#

8.2.2.2 Tietueen kopiointi

8.2.2.2.1 Postblit-konstruktori#

8.2.3 Rajapinnat#

8.2.4 Operaattoreiden kuormitus

8.2.4.1 Dynaamisuutta staattisesti tyypitetyssä kielessä

8.3 Perintä

8.3.1 Abstrakti luokka

8.3.2 Moniperintä

8.4 Arvio valituista ratkaisuista

9 Kielestä löytyvät lisäominaisuudet

9.1 Yksikkötestaus

9.2 Sisäänrakennettu assembler

9.3 Dokumentaatiokommentit#

9.4 Yhteensopivuus C ja C++ -kielten kanssa

9.5 SafeD

10 Yhteenveto

10.1 Käyttökohteet

2 D-ohjelmointikieli yleisesti

2.1 Taustaa

D-ohjelmointikielen historia alkaa vuodelta 1999. Ohjelmointikielten kääntäjiin erikoistunut ohjelmoija Walter Bright halusi kehittää c++ -kielelle vaihtoehdon, jolla ei olisi taaksepäin riippuuvuuksien tuomia rajoitteita.[1] Vuonna 2001 Brightin omistama yritys Digital Mars julkaisi version 0.0 D-kääntäjästä.[2]

Kieli kehittyi eri versioiden mukana, kunnes se vakiintui 1.0 julkaisussa vuonna 2007.[3] Tässä vaihessa kieli mahdollisti kolme ohjelmointitapaa: imperatiivisen, olio-ohjelmoinnin ja metaohjelmoinnin. Version 1.0 jälkeen kielen kehitys on jatkunut 2.0 kehityshaarassa.

D-kielen 2.0 versio lisäsi tuen funktionaaliselle ohjelmoinnille ja rinnakkaisohjelmointiin aktorimallin myötä. 2.0 kehityshaaraa kehitetään edelleen, mutta itse kielen katsotaan vakiintuneen Andrei Alexandrescun The D Programming Language -kirjan julkaisun myötä vuonna 2010.[4] Tämä dokumentti käsittelee D kielen 2.0-versiota.

Bright ei ole joutunut kehittämään kieltä yksin, vaan on saanut avukseen myös muita kehittäjiä. Erityisesti c++ -maailmassa tunnetun Alexandrescun liittyminen kehitystiimiin on lisännyt kiinnostusta kieleen. Siltikään D ei ole tällä hetkellä erityisen suosittu ohjelmointikieli. Tiobe-indeksi listasi D:n sijalle 33 maaliskuussa 2011.[5]

2.2 Toteutustapa

D käännetään käännös- ja linkitysvaiheiden kautta konekieleksi. Kääntäjistä on tällä hetkellä neljä eri toteutusta:[6]

D:tä voi käyttää myös ilman käyttäjälle näkyvää käännös- tai linkitysvaihetta. Esimerkiksi Linux-ympäristöissä voi tiedoston alkuun lisätä rivin "#!/usr/bin/dmd -run". Tämä mahdollistaa D:n käytön myös tulkatun kielen tapaan.

2.3 Esimerkki

// The following three lines import needed standard library modules

import std.algorithm;

import std.stdio;

import std.range;

/**

 * Finds the next number in Fibonacci sequence using iterative approach.

 * This is very near to valid c++ or java code, so it is not explained.

 */

int findNextIterative(int number)

{

        int previous;

        int result = 1;

        while (result <= number)

        {

                const int CURRENT = previous + result;

                previous = result;

                result = CURRENT;

        }

        return result;

}

/**

 * Finds the next number in Fibonacci sequence using functional approach

 */

int findNextFunctional(int number)

{

        // Nested predicate function to test the end condition,

        bool isLarger(int current)

        {

                return current > number;

        }

        // Creates a lazily built sequence of Fibonacci numbers. I.e. the

        // numbers are created when they are asked for.

        // Note the use of keyword auto for 'values' variable -> the type of the

        // variable is automatically recognized.

        auto values = recurrence!("a[n-1] + a[n-2]")(1, 1);

        // Use the standard library 'find' algorithm to seek the first value in

        // sequence that is larger than the given number.

        // The isLarger function is given as predicate function.

        // The algorithms in D return ranges. The front( ) method returns the

        // first (and only) value in the range.

        return find!(isLarger)(values).front( );

}

// Main function for the program. The (unused) command-line options are

// given as an array.

void main(string[] arguments)

{

        // Writes a prompt string to the user

        writeln("Give a number");

        // Reads number from standard input

        int number;

        scanf("%d", &number);

        // Calls both forms of the algorithm and outputs the results

        writefln("The number that follows in Fibonacci sequence");

        writefln("\tIterative style %d", findNextIterative(number));

        writefln("\tFunctional style %d", findNextFunctional(number));

}

3 Alkiorakenne

3.1 Tunnukset

Tunnukset D-kielessä voivat alkaa kirjaimella tai "_"-merkillä, jonka jälkeen voi tulla kirjaimia, "_" ja numeroita. Tunnus ei kuitenkaan voi alkaa kahdella peräkkäisellä "_"-merkillä(varattu). Esimerkiksi tunnus "__count" ei ole sallittu D:ssä.[7]

3.2 Varatut sanat

D-kielessä  on varsin  runsaasti varattuja sanoja. Seuraavassa kattava listaus:[8]        

                          

abstract

alias

align

asm

assert

auto

body

bool

break

byte

case

cast

catch

cdouble

cent

cfloat

char

class

const

continue

creal

dchar

debug

default

delegate

delete

deprecated

do

double

else

enum

export

extern

false

final

finally

float

for

foreach

foreach_reverse

function

goto

idouble

if

ifloat

immutable

import

in

inout

int

interface

invariant

ireal

is

lazy

long

macro

mixin

module

new

nothrow

null

out

override

package

pragma

private

protected

public

pure

real

ref

return

scope

shared

short

static

struct

super

switch

synchronized

template

this

throw

true

try

typedef

typeid

typeof

ubyte

ucent

uint

ulong

union

unittest

ushort

version

void

volatile

wchar

while

with

__FILE__

__LINE__

__gshared

__thread

__traits

3.3 Literaalit

D-kielestä löytyvät merkkijono-, merkki-, numeeriset- sekä liukuliteraalit.[9] 

3.3.1 Merkkijonoliteraalit[10]

Merkkijonoliteraali voi olla heittomerkkien sisässä oleva merkkijono, wysiwygstring, hexstring, rajatutmerkkijonot tai tokenstring.

3.3.1.1 Heittomerkkein suljettu merkkijono

Heittomerkein suljettu merkkijono tulee merkkien "" väliin.

"hello, world" // hello, world

"hello,\n world" // hello,

// world

3.3.1.2 wysiwygstring

Wysiwygstring kirjoitettaan merkkien r" ja " väliin.

r"hello,\n world" // hello,\n world

Vaihtoehtoinen tapa kirjoittaa wyswygstring on kirjoittaa se merkkien ``(backquote) väliin.

`hello,\n world` // hello,\n world

3.3.1.3 hexString

Hex dataa sisältävät merkkijonot voidaan kirjoittaa merkkien x" ja " väliin.

x"0A"

Esimerkki voidaan krjoittaa myös muodossa "\x0A".

3.3.1.4 Rajatutmerkkijonot

Rajatutmerkkijonot kirjoitetaan merkkien merkkien q" ja " väliin. Merkit joita voi käyttää erottimina ovat [], (), <> ja {};

q"(foo(xxx))" // foo(xxx)

3.3.1.5 TokenString

Tokenstring kirjoitetaan merkkien q{ ja } väliin.

q{foo} // foo

3.3.1.6 Postfix

Merkkijonon sulkevien merkkien “” jälkeen tulevalla loppumerkillä voidaan ilmaista merkkijonon tyyppi:

c -> char[]

w -> wchar[]

d -> dchar[]

Alla olevan merkkijonon perässä oleva c ilmaisee merkkijonon olevan tyyppiä char[].

"Hello, world"c // char[]

3.3.2 Merkkiliteraalit

Merkkiliteraalit kirjoitetaan merkkien '' väliin. Merkki voi olla mikä tahansa kirjain,numero tai escapesequence.[11]

'a' // a

'9' // 9

'\n' // rivinvaihto

3.3.3 Numeeriset literaalit

Numerot voidaan kirjoittaa decimaali-, binääri-, octaaali- tai hexamudoissa. Binäärilukua edeltää merkki 0b, octaalilukua 0 ja hexalukua 0x. [12]

0b1000 // 8

01000 // 512

0x1000 // 4096

Luettavuuden helpottamiseksi pitkiä lukuja voidaan ryhmitellä esim. tuhansiin _-merkin avulla.

10_000 // 10000

Luvut voivat päättyä merkkiin L,U tai molempiin, tällä tavoin voidaan kertoa luvun tyyppi.

12341234 // int

12341234L // long

12341234U // uint

12341234UL // ulong

3.3.4 Liukuliteraalit[13]

Liukuluvut kirjoitetaan decimaali tai hexamuodoissa. Liukulukujen luettavuuttaa voidaan myös helpottaa _-merkillä. Hexaluku alkaa 0x ja hexaluvun exsponenttia edeltää P tai p. Decimaaliluvun exspnenttia edeltää e,E.

0x54 // 84

0x54p3 // 672

Liukuluku voi päättyä merkkeihin f,F,l,L tai i. F tai f suffix ilmaisee luvun olevan liukuluku ja L tai l suffix ilmaisee luvun olevan reaaliluku. Liukuluku, jonka suffix on i, on imaginaariluku.

3.4 Kommentit

Kommentteja on kolmea tyyppiä:

Yhden rivin kommentti //

Useamman rivin kommentti /* */

Sisäkkäiset kommentit /+ +/

/+

  /*

    comment...

 */

+/

3.5 Arvio ratkaisujen haitoista ja hyödyistä

Kielessä on varsin runsaasti varattuja sanoja. Tämä saattaa olla varoitusmerkki liian isoksi pullahtaneesta kielestä. Toisaalta monet ominaisuuksista ovat selkeitä parannuksia c++ -kielen vastineihin. Esimerkiksi käännettävän kielen rajoittaminen version-lohkoilla esiprosessorin sijaan tuottaa selkeämpää koodia. Samoin funktio-viitteen esittäminen function-avainsanalla tai delegate-avainsanalla tekevät lopputuloksesta luettavampaa.

Literaalivakioiden esitykseen on myös varsin monta tapaa. Nämä voivat helpottaa luettavuutta, tai sitten tehdä hankalammaksi, mikäli lukija ei ole tiettyyn tapaan tottunut. Esimerkiksi alaviivat numerovakioissa voivat olla aluksi hämääviä, varsinkin kun niitä voi sisällyttää mukaan vapaavalintaisiin kohtiin. Wysiwyg-tekstivakiot ovat varmastikin käteviä, mutta `-merkkien käyttö menee melko varmasti sekaisin '-merkkien kanssa.

Näistä esimerkeistä tulee yleisvaikutelma, että kieleen on haluttu mukaan kaikenlaista "pientä kivaa", joka houkuttelisi ohjelmoijia kielen pariin. Sen arvioiminen, että onko tässä onnistuttu, vai onko kieli mennyt liian monimutkaiseksi, vaatisi pidempiaikaisen käyttökokemuksen.

4 Tunnusten näkyvyysalueet

4.1 Lohkorakenne[14]

Lohko rajataan D:ssä merkeillä {} ja se voi koostua lausekkeista.

{

    int x = 8;

    writefln("x is %s",x); // x is 8

}

Lohkot voivat olla sidottuja joko johonkin lauseeseen, funktioon tai luokkaan. Ne voivat olla myös irrallisina funktion sisällä, jolloin ne vain luovat nimettömän näkyvyysalueen.

Pääasiallisesti lohkon ulkopuoliset nimet ovat näkyvillä lohkon sisällä, mutta lohkon ulkopuolelta ei ole näkyvyyttä sen sisälle. Poikkeuksena ovat luokkien ja tietuiden jäsenet, mikäli näiden näkyvyyssäännöt näin sallivat (tästä tarkemmin luvussa 8). Samaten static if -lohkon sisäiset nimet ovat näkyvissä lohkon ulkopuolelle.

4.2 Sidonta

D-kielessä tunnukset sidotaan staattisesti.

Lohkon sisällä voidaan määritellä uusia lohkoja, joissa näkyvät globaalit muuttujat sekä ulkopuolisissa lohkoissa esitellyt lokaalit muuttujat. Sisäkkäisen lohkon muuttujat eivät näy ulkopuolisissa lohkoissa eikä niissä voida esitellä uudestaan ulkopuolisten lohkojen lokaaleja muuttujia.[15]

{

    int x = 8;

    int y= 0;

    {

        x = 9;

        int y = 1; // not allowed

    }

    writefln("x is %s",x); // x is 9

}

Lohkossa määritellyissä aliohjelmissa sekä tietueissa(struct) voidaan esitellä uudestaan ulkopuolisten lohkojen lokaalit muuttujat.

{

    int x = 8;

    int y= 0;

    {

        x = 9;

        void func(){

            int y = 1; // allowed

        }

    }

    writefln("x is %s",x); // x is 9

}

4.3 Sulkeumat[16]

D-kielessä funktiot ovat ensimmäisen luokan arvoja. Kielessä on siis täysi tuki sulkeumille. Sulkeuma luodaan D-kielessä delegate-tyypillä. Se voidaan luoda esimerkiksi explisiittisellä viitteellä funktioon:

bool delegate(int) closureAsNestedFunction(int compareTo)

{

    // Nested function that does the actual job

    bool isBigger(int value)

    {

        // Within we are referencing to variable 'compareTo'

        return value > compareTo;

    }

    // Return reference to function, which will create a closure

    return &isBigger;

}

Helpompaa voi kuitenkin olla käyttää anonyymiä funktiota:

bool delegate(int) closureAsAnonymousFunction(int compareTo)

{

    return (int value){ return value > compareTo; };

}

Vielä helpompaa on käyttää varauttua sanaa auto, jolloin voi jättää palautetun arvon tyypin määrityksen kääntäjän huoleksi.

auto closureAsAnonymousFunctionWithDeducedType(int compareTo)

{

    return (int value){ return value > compareTo; };

}

Kahdessa jälkimmäisessä esimerkissä on huomioitava, että anonyymit funktiot ja alias-toiminnallisuus ovat erillisiä toiminnallisuuksia sulkeumiin nähden. Niitä käytetään myös muissa yhteyksissä. Ne ovat kuitenkin hyödyllisiä sisällyttää sulkeuma-esimerkkeihin, koska osoittavat sulkeumien helppokäyttöisyyttä.

Samaten lienee hyvä mainita, että delegate-toiminnallisuus ei sinänsä rajoitu sulkeumiin. Delegate-oliolla pystyy viittaamaan mihin hyvänsä ei-staattiseen funktioon, joten sillä pystyy toteuttamaan sulkeuman, mutta sitä käytetään myös muihin tarkoituksiin.

4.3.1 Monimutkaisempi esimerkki

/**

 * Creates closure that is able to convert x,y coordinates on chess board to

 * chess notation. The coordinates start from top left corner. Thus

 *  0, 0 -> a8, 7, 7 -> h1

 */

string delegate(uint, uint) createConverter(uint boardSize)

{

    // Nested function, which converts column to chess notation column

    // The second line is an example of contract programming, where an assertion

    // exception will be thrown, if board size is exceeded.

    char xToColumn(uint x)

    in { assert (x < boardSize); }

    body

    {

        const uint FIRST_COLUMN = 'a';

        // Example of casting, unsigned integer to character

        return cast(char)(FIRST_COLUMN + x);

    }

    // Similar to previous, except converting to column

    string yToRow(uint y)

    in { assert (y < boardSize); }

    body

    {

        // Example of integer to string conversion. The 'to' function is a

        // template function from standard library. Exclamation mark tells it

        // that we want to define template type, which is then string.

        return to!string(8 - y);

    }

    // Returning a closure, which takes to unsigned integers as parameters and

    // returns string. Return value of closure is deduced by compiler.

    // Note that the closure will remember both of the nested functions. And

    // those functions will remember the value of local variable boardSize.

    return (uint x, uint y) { return xToColumn(x) ~ yToRow(y); };

}

4.4 Ensimmäisen, toisen ja kolmannen luokan arvot

Arvo on ensimmäisen asteen arvo, jos se voidaan välittää parametrina, palauttaa funktiosta ja asettaa muuttujaan. Toisen asteen arvoja voidaan välittää parametrina, mutta ei palauttaa funktiosta, eikä asettaa muuttujaan. Kolmannen asteen arvoa ei voi edes välittää parametrina.[17]

Kaikki D:n perustyypit kuten int, char ja double, ovat ensimmäisen asteen arvoja. Perustyypeiksi lasketaan myös taulukot, mukaan lukien myös merkkijonotaulukot ja assosiatiiviset taulut. Samaan kategoriaan sisältyvät myös oliot. Funktiot ja metodit ovat myös ensimmäisen asteen arvoja.

On hankalaa keksiä esimerkkiä toisen asteen arvoista D-kielessä. Ehkä arvojen tyyppitiedon voisi sellaiseksi tulkita? Metaohjelmoinnissa voidaan tyyppitietoa välittää parametrina käännöksen aikaisiin algoritmeihin. Toisaalta käännöksen aikaisista algoritmeista pystytään myös palauttamaan tyyppitietoa. Ehkä tyyppitieto pitäisi tulkita ensimmäisen asteen arvoksi käännöksen aikana, mutta kolmannen asteen arvoksi ajon aikana.

Kolmannen asteen arvoiksi lasketaan D-kielessä esimerkiksi operaattorit (kuten '+', '-' ja '=='). Siten ole mahdollista esimerkiksi välittää itse operaattoria algoritmille, vaan tarkoitusta varten tulee luoda funktio/delegaatti, jossa operaatiota käytetään. Toinen esimerkki on goto-lauseen yhteydessä käytettävä tunnus (label).

4.5 Arvio ratkaisujen eduista ja haitoista

Tunnusten näkyvyyssäännöt noudattavat lähes täysin tunnetumpien kielien, kuten C++ ja Java, vastaavia sääntöjä. Lisänä on kuitenkin mahdollisuus esitellä funktioita funktioiden sisällä. D ei siis ole litteä ohjelmointikieli. Tämä tuo enemmän ohjelmointitekniikoita käyttäjän ulottuville.

Sulkeumien tuki on toteutettu varsin mallikkaasti D-kielessä. Niiden kätevä käyttö edellyttää kuitenkin myös muiden kielen ominaisuuksien osaamista. Sulkeumien käyttö ilman anonyymejä funktioita ja automaattista paluuarvojen tulkintaa tekee koodista turhan monisanaista ja kömpelöä. Ominaisuudet kuitenkin tukevat toisiaan ja pienellä harjoittelulla on sulkeumien käyttö kätevää.

5 Kontrollin ja/tai laskennan ohjaus

D-kielessä on varsin useita eri tapoja ohjaamaan ohjelman kulkua. Useat näistä on noudattavat muista imperatiivisista kielistä tuttuja tapoja. Mutta on mukana myös erikoisuuksiakin, kuten scope lauseke. Myös harvinaisempaa käännöksen aikaista ohjausta tuetaan varsin kätevästi.

5.1 Ajonaikaiset ohjaustavat

5.1.1 && -operaattori lauseessa[18]

D-kielessä && -operaattoria voidaan käyttää myös ehto-lauseen ulkopuolella, koska operaattorin oikealle puolelle voidaan käyttää void-arvoista lausetta. Esimerkiksi:

string line = getContentsOfStringFromMysteryWell( );

line == "searched" && writeln("Found searched line");

5.1.2 || -operaattori lauseessa[19]

Vastaavalla tavalla voidaan käyttää myös || -operaattoria:

string line = getContentsFromPlaceYouCannotReallyTrust( );

line.length > 0 || line = "a default value that does not cause problems";

5.1.3 if-else[20]

D-kielen if-else lause on vastaava kuin muissa C-perustaisissa kielissä. If- ja else-lohkot ovat joko yksirivisiä tai ne täytyy ympäröidä aaltosuluilla. Else-lohkoja voi ketjuttaa lisäämällä else if lauseita. Tyhjä if-lohko on estetty.

if (theSkyIsFalling( ))

    findSomethingToHideUnder( );

else if (theRomansAreComing( ))

    runToTheGates( );

else

{

    huntBoar( );

    tieBard( );

    relax( );

}

if (emptyIfBlocksArePrevented( )); // Compilation error

if (youReallyReallyWantEmptyBlockWriteItExplicitly( )) { }

5.1.4 Ehdollinen lauseke[21]

Mikäli halutaan asettaa arvo jonkin ehdon mukaan, voidaan se tehdä ehdollisella lausekkeella.

value = value > maximumValue ? maximumValue : value;

5.1.5 switch-case[22]

Switch-case -lause mahdollistaa monivalinnan yhden lauseen arvon perusteella. Valintamahdollisuuksia ovat perinteisten oletus- ja yhden arvon valinnan lisäksi mahdollisuus luetella lista arvoista ja arvoväli. Kuten c++- ja Java-kielissä, jokaisen valinnan jälkeen tulee olla break-lause, tai siirrytään seuraavaan valintaan. Jos yksikään valintalauseista ei toteudu, seuraa ajonaikainen virhe. Valintalauseen arvo voi olla numero, enumeraatio tai merkkijono.

void printSomethingAboutNumber(int number)

{

    switch (number)

    {

    case 0:

        writeln("number is 0");

        break;

    case 1, 7, 8:

        writeln("number is either 1, 7 or 8");

        break;

    case 2: .. case 6:

        writeln("number is between 2-6");

        break;

    default:

        writeln("Unrecognized number");

    }

}

5.1.6 final switch[23]

Normaalin switch-case -lause muunnelma on final switch -lauseke. Erona on, että sitä voidaan käyttää vain enumeraatioiden kanssa varmistamaan, että kaikki enumeraatiot on käyty läpi. Yhden tai useamman enumeraatio-arvon poisjättäminen aiheuttaa käännösvirheen.

enum Decision { yes, no, maybe }

void printDecision(Decision decision)

{

    final switch (decision)

    {

        case Decision.yes: writeln("affirmative"); break;

        case Decision.no: writeln("negative"); break;

        case Decision.maybe: writeln("kinda"); break;

    }

}

5.1.7 while[24]

While-lause toistaa sisältämänsä lohkon lausekkeet niin kauan kuin sen ehto on voimassa.

void printNumberANumberOfTimes(int number)

{

    while (shouldThePrintingGoOn( ))

    {

        writeln(number);

    }

}

5.1.8 do-while[25]

Do-while -lause on muuten kuin while-lauseke, mutta ehto tarkistetaan vasta lohkon suorituksen jälkeen.

void printNumberAtLeastOnce(int number)

{

    do

    {

        writeln(number);

    }

    while (shouldThePrintingGoOn( ))

}

5.1.9 for[26]

For-lause toimii kuten c++- ja Java-kielissä.

void printNumberTenTimes(int number)

{

    for (int i = 0; i < 10; ++i)

    {

        writeln(number);

    }

}

5.1.10 foreach numeroarvoille[27]

Silmukan toisto on monesti helpompaa foreach-lauseella. Siinä ei ole erillisiä alustus-, ehto- eikä toistolauseita. Sen sijaan annetaan arvoväli sekä muuttuja, mihin arvo tallennetaan.

void printNumberTenTimes(int number)

{

    foreach (i; 0 .. 10) // Looping from 0 to 9. The range does not include 10.

    {

        writeln(number);

    }

]

5.1.11 foreach tietorakenteilla[28]

Arvoalueiden lisäksi foreach-lauseella voidaan käydä läpi myös tietorakenteita. Tämä onnistuu suoraan D-kielen valmiille tietorakenteille kuten taulukolle ja assosiatiiviselle taulukolle. Lisäksi itse toteutettujen luokien oliota voi iteroida foreach-lauseella toteuttamalla tähän vaadittavat operaattorit.

// Iterates through characters of a string

auto text = "Some text";

foreach (c; text)

{

    writeln("Current character is ", c);

}

// Iterates through the values stored in associative array

auto map = [1 : "0", 2 : "1", 3 : "1", 4 : "2", 5 : "3", 6 : "5", 7 : "8"];

foreach (current; map) // Note: order of iteration is not defined

{

    writeln("Current ", current);

}

Halutessaan foreach-lauseessa voi esitellä sekä iteroidun arvon, että sen hakemiseen käytetyn indeksin/avaimen.

auto text = "Some text";

foreach (c, i; text)

{

   writeln("Character ", i, " is ", c);

}

// Example of iterating through an assosiative array

auto map = [1 : "0", 2 : "1", 3 : "1", 4 : "2", 5 : "3", 6 : "5", 7 : "8"];

foreach (current, key; map) // Note: order of iteration is not defined

{

   writeln(key, " : ", current);

}

5.1.12 Silmukan keskeytys[29]

Silmukan lohkon suorituksen voi keskeyttää joko break-lausekkeella, jolloin hypätään silmukasta pois, tai continue-lauseella, jolloin hypätään seuraavaan silmukan suoritukseen. Molemmille voi myös antaa tunnuksen, jolloin suoritus hyppää tunnuksella merkitylle riville.

string findCharactersFromString(string searchFrom, string characters)

{

    string result;

  outer: foreach (searchedCharacter; characters) // marked with label

    {

        foreach (candidate; searchFrom)

        {

            if (searchedCharacter == candidate)

            {

                result ~= candidate;

                // Continues outer loop, because of name. Plain 'continue' would

                // continue the inner loop.

                continue outer;

            }

        }

    }

    return result;

}

5.1.13 Rekursio

D-kielessä rekursiiviset funktiokutsut ovat mahdollisia. Funktio voi kutsua itseään tai toisia funktioita, jotka kutsuvat alkuperäistä funktiota. D-kieleen on toteutettu häntä-rekursion optimointi, mikä optimoi pois ylimääräiset muistinvaraukset aktivaatiotietuepinosta. [30] Tämä helpottaa kielen käyttöä funktionaaliseen ohjelmointiin.

// Recursive function, which prints numbers to console until max is received

void printNumber(int current, immutable int max)

{

    if (current == max)

    {

        return;

    }

    writeln(current);

    printNumber(current + 1, max);

}

5.1.14 goto[31]

D-kielestä löytyy myös pahamainen [32] goto-lause. Lauseella voidaan siirtää suoritus toiselle koodiriville. Uusi paikka on merkitty tunnuksella ja kaksoispisteellä. Merkitty rivi tulee olla näkyvissä samasta funktiosta kuin goto-lause. Kahta lausetta ei saa merkitä samalla tunnuksella, eikä goton suoritus saa hypätä arvon määrityksen yli. Dmd-kääntäjä ei kylläkään tällä hetkellä toteuta jälkimmäistä rajoitusta [33].

void gotoExample( )

{

    int i = 0;

  loop: writeln(i); // Labeled statement. Used to implement a loop

    if (++i < 10)

    {

        goto loop; // Moves execution back to line marked with "loop"

    }

}

5.2 Käännöksen aikaiset ohjaustavat

5.2.1 static if[34]

D-kielen käännökseen ei liity esiprosessointia, mutta static-if lauseella voidaan saada aikaan vastaavaa toiminnallisuutta. Lohko, joka alkaa static if -lauseella, käännetään vain, jos lauseen ehto täyttyy. Ehdon arvo tulee olla käännöksen aikana määriteltävissä. Se voi olla vertailu literaaleilla, literaarien laskuoperaatioilla, tai funktioilla, joille kääntäjä laskee arvon käännöksen aikana.

Erillistä static-else lausetta ei ole. Haluttaessa voidaan static if lohkon vaihtoehto esittää normaalilla else-lauseella.

Seuraavassa esimerkissä esitellään kuvitteellinen luokka, joka on turvallinen yhdestä säikeestä käytettynä, mutta joka tarvitsee synkronointia, mikäli sitä käytetään useammasta säikeestä. Synkronointi voi hidastaa suoritusta jonkin verran. Voi olla kätevää kytkeä se pääle tai pois käännöksen aikana. Esimerkissä tämä on hoidettu bool tyyppisellä template-parametrillä. Ohjelmoija voi koodia kirjoittaessaan päättää synkronoinnin käyttöön välittämällä true-arvon template-parametrina.

class InventedApi(bool isSynchronizationNeeded)

{

    private void lock( )

    {

        // Implementation will be compiled only, if multi-thread support is on

        static if (isSynchronizationNeeded)

        {

            // Do the locking

        }

    }

    private void unlock( )

    {

        // Implementation will be compiled only, if multi-thread support is on

        static if (isSynchronizationNeeded)

        {

            // Do the unlocking

        }

    }

    public void doTheOperation( )

    {

        // In single-thread code, calls to lock and unlock are optimized away

        lock( );

        // Imagine here an operation, which would need synchronization

        unlock( );

    }

}

Static if -lohkon erikoisuus on, että se ei luo uutta näkyvyysaluetta. Muuten ei olisi mahdollista päästä käsiksi lohkossa esiteltyihin arvoihin.

5.2.2 mixin[35]

D-kielessä pystyy mixin-lauseen avulla määrittelemään uusia ominaisuuksia kieleen käännöksen aikana. Lauseelle annetaan käännöksen aikana määriteltävissä oleva teksti. Tekstin tulee olla laillista D-koodia. Tällöin annettu koodi käännetään ikään kuin se olisi ollut mixin-lauseen kohdalla. Yksinkertaisin esimerkki lienee seuraava:

writeln(mixin("1 + 2")); // The computation is done at compilation time

Hyödylliseksi tämän tekee se, että koodin muodostaminen pystytään tekemään itse D-kielellä. Eli voidaan kirjoittaa koodia D-kielen generoimiseen ja tuottaa generaation tulos suoraan käännettyyn binääriin. Seuraavassa esimerkki, joka laskee 50 ensimmäistä Fibonaccin lukua käännöksen aikana tauluun.

import std.stdio;

import std.conv;

// Create D code for a table of fibonacci numbers created at compilation time

// Result is for example like this:

// immutable ulong[5] fibonacciTable = [ 1, 2, 3, 5, 8, ];

string createFibonacciTableInCode(string name, uint count)

{

    string result = "immutable ulong[" ~ to!string(count) ~

                    "] " ~ name ~ " = [ ";

    ulong previous;

    ulong current = 1;

    foreach (i; 0..count)

    {

        ulong value = previous + current;

        previous = current;

        current = value;

        result ~= to!string(value);

        result ~= ", ";

    }

    result ~= "];";

    return result;

}

void main( )

{

    mixin(createFibonacciTableInCode("fibonacciTable", 50));

    // During runtime, the cost of access is just an array look-up

    foreach (k, i; fibonacciTable)

    {

        writeln(k, ":", i, " ");

    }

}

5.3 Valittujen ratkaisujen arviointia

Tässä vaiheessa lienee jo käynyt selväksi, että D on rikas kieli. Siihen on toteutettu monen moista. Ilman goto-lausetta voisi ehkä elää, mutta muuten ratkaisut vaikuttavat hyviltä. Kielestä löytyy samat perusrakenteet (for, while, jne) kuin muistakin imperatiivisista kielistä. Monista kielistä löytyy myös foreach, mutta D:ssä se on käytännöllisempi kuin joissain: siinä pystyy silmukoimaan numerovälejä kätevästi ja iteroimaan sekä arvoa, että indeksiä.

Käännöksen aikaiset kontrollirakenteet ovat myös mielenkiintoisia. Monissa kielissä vastaavaa ei ole saatavilla. Joissain kielissä (kuten C ja C++) vastaavan saa aikaan makroilla ja/tai template-koodilla, mutta näiden oikein käyttö vaatii taitoa.

Erityisesti mixin vaikuttaa mielenkiintoiselta ratkaisulta. Sillä pystyy periaatteessa laajentamaan D-kieltä haluamallaan tavalla. Jos asiassa ei innostu liikaa, voi lopputulos olla käytännöllinen ja tyylikäs. Ainakin toistaiseksi toteutus on kuitenkin hieman buginen. Esitetyssä esimerkissä merkkijonojen yhdistämisen joutui jakamaan kahdelle riville, jotta kääntäjä hyväksyi sen. Tällaisten kauneusvirheiden kanssa kuitenkin pystyy kuitenkin elämään.

6 Tietotyypit

D -kielessä tyyppitarkastukset tehdään käännösaikana eli D on staattisesti tyypitetty ohjelmointikieli. Tunnusten tyypiä ei kuitenkaan ole pakko välttämättä määritellä, vaan sen voi jättää kääntäjän pääteltäväksi kirjoittamalla tunnuksen eteen tyypin sijaan avainsanan auto.

Esimerkki auto avainsanan käytöstä on luvun 3 fibonazzin lukuja laskevan ohjelman funktiossa findNextFunctional.

D on vahvasti tyypitetty ohjelmointi kieli eli  D:ssä on määritelty sääntöjä, jotka määrittävät minkä tyyppisilla datalla operaatiota voidaan esim. minkä tyyppisillä data alkioilla voidaan suorittaa yhteenlaskuja.[36] 

6.1 Perustietotyypit

Tässä luvussa esitellään D-kielen perustietotyypit, joita ovat totuusarvo (boolean), kokonaisluvut (byte, short, int, long), reaaliluvut (float, double, real), kompleksiluvut (cfloat, cdouble, creal), imaginääriluvut (ifloat, idouble, ireal) ja merkit/merkkijonot(char,

wchar, dchar).[37]

Kuvaus

Arvoalue

Alkuarvo

bool

Totuusarvo

true,false

false

byte

8 bittiä

-128  - 127

0

ubyte

etumerkitön 8 bittiä

0 - 255

0

short

16 bittiä

-32768 - 32767

0

ushort

etumerkitön 16 bittiä

0 - 65535

0

int

 32 bittiä

-2,147,483,647 - 2,147,483,647

0

uint

etumerkitön 32 bittiä

0 - 4,294,967,295

0

long

64 bittiä

-9,223,372,036,854,775,808 - 9,223,372,036,854,775,807

0L

ulong

etumerkitön 64 bittiä

0 - 18,446,744,073,709,551,615

0L

cent

128 bittiä (tulevaisuutta varten)

0

ucent

etumerkitön 128 bittiä (tulevaisuutta varten)

0

float

32 bittiä

1.18 10^-38 - 3.40 10^38

float.nan

double

64 bittiä

2.23 10^-308 - 1.79 10^308

double.nan

real

suurin laitteiston tukema desimaaliluku (x86 CPU 80 bittiä)

real.nan

ifloat

imaginääri float

float.nan * 1.0i

idouble

imaginääri double

double.nan * 1.0i

ireal

imaginääri real

real.nan * 1.0i

cfloat

kompleksi float

float.nan + float.nan * 1.0i

cdouble

kompleksi double

double.nan + double.nan * 1.0i

creal

kompleksi real

real.nan + real.nan * 1.0i

char

etumerkitön 8 bittinen UTF-8

0xFF

wchar

etumerkitön 16 bittinen UTF-16

0xFFFF

dchar

etumerkitön 32 bittinen UTF-32

0x0000FFFF

D: stä löytyy myös String merkkijonojen käsittelyyn. String on alias muuttumattomien merkkien taulukolle (char[]).

string car = “volvo”;  // type of car is unmutable(char)[]

6.2 Muut tietotyypit

6.2.1 Taulukot

Taulukkoja on kahta tyyppiä d: ssä array[38] sekä associative array[39].

Array on taulukko, jolle voidaan määritellä alkioiden tyyppi ja jonka alkioita indeksoidaan luvulla (int).

int[3] arr;     // int array size tree

arr[0] = 100;   // sets 100 to array position 0

Associative array on taulukko, jolle määritellään siihen tallennettavien alkioiden tyyppi sekä alkioiden indeksointiin käytettävän “avaimen” tyyppi.

int[string] arr;  // int array thats is index with string

arr[“day”] = 23;  // sets value 23 for key day

6.2.2 Tietueet (struct ja union)[40]

Struct ja union vastaavat c++ vastaavia rakenteita. Struct ja union erottaa toisistaan se, että unionin jäsenmuuttujat osoittava samalle muistialueelle eli yhden jäsenmuuttujan arvon muuttaminen muuttaa myös toisten muuttujien arvon.

struct car{ int price, year; }     // defines struct car

car volvo;

volvo.price = 9999;                // sets price to 9999

volvo.year = 2000;                 // sets year to 20000

union car2 { int price, year; }    // defines union car2

car2 mercedez;

mercedez.price = 9999;             // sets price and year to 9999

mercedez.year = 2000;              // sets price and year to 2000

6.2.3 Osoittimet (function ja delegate)

Osoittimia voidaan käyttää osoittamaan muuttujien muistipaikkoihin (pointer) sekä funktioihin (function ja delegate).

Osoitin luodaan c++:n tyyliin lisäämällä * tietotyypin perään.

int balance = 1456;

int *point_balance = &balance;           // creates pointer to datatype int

writefln(“balance: %s”,*point_balance);  // prints “balance: 1456”

Osoitin staattiseen funktioon luodaan avainsanalla function.

int square(int x) {  return x*x;   }   // defines function square

int function(int) fpSquare = &square;  // creates pointer to function square

writefln(“2*2 is %s”,fbSquare(2));     // prints “2*2 is 4”

Delegate-osoitin eroaa funtion-osoittimesta siinä, että se voi osoittaa muuhunkin kuin staattiseen funktioon. Se voi osoittaa luokan metodiin tai esimerkiksi sulkeumaan.

class Car {                                       // defines class Car

    private string model;            

    this(string model) { this.model = model; }  

    string getModel() { return model; }    

}

Car volvo = new Car("s70");                      

string delegate() fpModel = &volvo.getModel;      // fpModel points to volvo and

                                                  // and a member funtion of Class

                                                  // car    

writefln(fbModel());                              // prints “s70”

6.2.4 Alias (alias)[41]

D:ssä  on mahdollista luoda alias jollekin tietotyypille. Esimerkiksi tietotyyppiä int voidaan halutessa kutsua number:ksi. Aliaksen luomiseen käytetään avainsanaa alias.

alias int number;     // creates alias number for int

number count = 89;

6.2.5 Lueteltu tietotyyppi(enum)

Lueteltu tietotyyppi (enum)[42] vastaa c++:n lueteltua tietotyyppiä. D:ssä tietotyypille voi määritellä tyypin, joko kertomalle määrittelyn yhteydessä tai alustamalla tietotyypin ensimmäisen arvon, jolloin tästä tulee tietotyypin tyyppi. Luetellun tietotyypin vakio tyyppi on int.

enum numbers { a, b, c}   // creates enum with values a=0, b=1 and c=2

6.3 Tyyppimääreet

D-kielessä on myös muutamia tyyppimääreitä, joilla voidaan rikastuttaa tyyppijärjestelmää. Määreitä voidaan antaa muuttujille ja luokkien metodeille.

Määrittelemällä muuttuja const-tyyppiseksi, sen sisältöä ei voida muokata kyseisen muuttujan kautta.[43] Tällöin voidaan rajata aluetta, jolla muuttujan kautta pystytään muokkaamaan ohjelman tilaa. Johdonmukaisesti käytettynä tästä on apua ohjelmointivirheiden estämiseen ja tutkimiseen.

Mikäli halutaan muuttujan arvo pysymään samana koko ohjelman suorituksen ajan, sille voidaan lisätä immutable-arvo.[44] Tätä käytetään usein funktionaalisessa ohjelmoinnissa, missä koetetaan välttyä täysin tilan muunnoksilta. Samoin siitä on etua rinnakkaisuudessa, sillä muuttumattomaksi taattua tietoa voidaan turvallisesti jakaa säikeiden välillä. Ratkaisun toimivuuden varmistaa immutable-määreen transitiivisuus: määre säilyy riippumatta tavasti, jolla tietoon päästään käsiksi.[45] Muuttumattomaksi määritellyn olion kaikki jäsenet ja näiden jäsenten jäsenet on määritelty muuttumattomiksi. Muuttumattomatonta tietoa ei myöskään voi muuttaa viitteen tai taulukon kautta.

7 Toiminnallisuuden kapselointi

7.1 Funktiot[46]

Funktiot määritellään c++:sta tuttuun tapaan. Aluksi kerrotaan funktion paluuarvon tyyppi, funktion nimi, mahdolliset parametrit ja lopuksi funktion ohjelmakoodi. Funktion paaluarvon voi D:ssä jättä kääntäjän päätettäväksi antamlla paluuarvon tyypiksi auto. Mikäli funktio ei palauta mitään asetetaan sen paluuarvon tyypiksi void.

int sum(int a, int b) {        // function sum, with 2 parameters and return value type int

    return a+b;

}

auto sum(int a, int b) { // function sum, with 2 parameters and return value

    return a+b;          // auto type of the return value is derived from return

                         // statement.

}

void printSum(int a,int b){  // function printSum, with 2 parameters and type

    writeln(“Sum: %s”,a+b);  // is void because function has no return value.

}

7.1.1 Ref -funktio

Funktiolle, jonka halutaan palauttavan osoittimen muuttujaan, täytyy lisätä avainsana ref tyypin eteen.

ref int getInt() {       // function with keyword ref can return reference

    int number = new int;        

    return *number;                      

}

7.1.2 Pure -funktio

D:ssä on myös niin sanottu pure -funktio. Funktio, joka ei muuta ohjelman tilaa tai käsittele globaaleja muttuujia, voidaan määritellä pure funktioksi lisäämällä funktion tyypin eteen avainsanan pure.

pure int sum(int a,int b) {        // pure function sum, with 2 parameters and return value type int

    return a+b;                             // function uses only parameters.

}

7.1.3 Inout funktio

Inout avainsanaa voidaan käyttää tarkoittamaan mitä tahansa mutable, const tai immutable, funktioden paluuarvoina tai parametreinä.

Alla on kolme funktiota, joista jokainen saa parametrinä normaalin mutablen, const tai immutablen taulukon, sekä arvoalueen mikä väliltä palauttaa arvot taulukosta:

int[] valuesBetween(int[] a, int x, int y) { return a[x .. y]; }

const(int)[] valuesBetween((const(int)[] a, int x, int y) { return a[x .. y]; }

immutable(int)[] valuesBetween(immutable(int)[] a, int x, int y)

{ return a[x .. y]; }

Yllä olevat funktiot voidaan korvata yhdellä funktiolla inout avainsanan avulla:

inout(int)[] valuesBetween((inout(int)[] a, int x, int y) { return a[x .. y]; }

7.1.4 Oletusarvot

 Funktion parametreille voidaan myös määritellä oletusarvot, kuten alla olevassa esimerkissä, jossa parametrille a annetaan oletusarvoksi 0 ja parametrille b 5.

int sum(int a=0,int b=5) { // Function sum, with 2 parameters. First parameter has

    return a+b;            // default value 0 and second default value 5.

}

sum(6);                     // same as calling sum(6,5);

7.2 Parametrien välitys[47]

Funktion parametrit voi D:ssä olla c++ kielen tapaan arvo-, osoite- tai viiteparametrejä ja ne määritellä c++:sta tuttuun tapaan.

int sum(int a,int b) {    // function with two value parameters

    return a+b;

}

// function with two address parameters to int variables

int sumWithPointers(int *a, int *b) {

    return *a+*b;

}

// function with two reference parameters to int variables

int sumWithReferences(int &a, int &b) {

    return a+b;

}

7.3 Virheiden hallinta

Oikein toimivassa ohjelmassa varsin suuri osa ohjelmakoodista keskittyy virheiden hallintaan. Osa virheistä johtuu käyttäjistä tai käyttöympäristöstä ja ohjelman tulee pystyä käsittelemään nämä hallitusti. Kenties vielä suurempi osa johtuu ohjelmoijien itse tekemistä virheistä ja vääristä olettamuksista.

D-kieli käsittelee näitä molempia poikkeuksien kautta. Tosin poikkeukset on jaettu kahteen alakategoriaan sen mukaan onko tilanne ohjelmointivirhe, vai jokin muu virhe. Molempia varten oma luokkahierarkiansa, joka periytyy yhteisestä kantaluokasta Throwable.

Vaikka molemmat ovat toteutettu poikkeuksina, niitä käsitellään ja käytetään eri tavalla.

7.3.1 Poikkeukset[48]

Poikkeksena voidaan heittää mikä tahansa olio, joka periytyy Throwable-luokasta. Käyttäjiä kehoitetaan kuitenkin perimään omat poikkeuksensa Exception-luokasta (joka on siis Throwable luokan aliluokka). Muut Throwable-luokan perilliset on tarkoitettu ohjelmointivirheiden käsittelyyn, mistä myöhemmin lisää.

Poikkeukset ovat periaatteessa samanlaisia kuin esimerkiksi Javassa. Poikkeuksien heittäminen tapahtuu throw-lauseella, ne voidaan käsitellä catch-lohkossa ja finally-lohkossa voidaan suorittaa operaatioita, jotka tulee tapahtui poikkeuksista riippumatta.

class Query

{

    // …

    void execute( )

    {

        throw new Exception("Query failed");

    }

    // …

}

string getDataFromFantasyDatabaseApi( )

{

    DataBase database = new DataBase( );

    try

    {

        Query query = new Query("select * from table");

        try

        {

            return query.execute( );

        }

        // finally block will make sure the query is able to uninitialize itself

        finally

        {

            query.close( );

        }

    }

    // this catch block will print the exception and propagate to higher level

    //  ...bad practice, but good to show as an example

    catch (Exception exception)

    {

        writeln(exception);

        throw exception;

    }

    // finally block to ensure that the database connection is closed

    finally

    {

        database.close( );

    }

}

D-kielessä on myös toteutettu poikkeuksien automaattinen linkitys. Mikäli poikkeuksien käsittelyssä tapahtuu uusi poikkeus, tallennetaan tämä linkitettyyn listaan. Ylemmällä tasolla voidaan kaikki poikkeukset käydä läpi ilman, että tietoa virheistä on kadonnut matkan varrella. Tämä myös auttaa esimerkiksi C++ -kielen (missä uuden poikkeuksen heittäminen on kiellettyä) rajoitukseen, joka estää poikkeuksien käytön destruktoreissa.

Poikkeuksen seurauksena palataan lähimpään catch-lohkoon, joka voi käsitellä poikkeuksen. Jos sellaista ei löydy, ohjelma loppuu ja palauttaa virhekoodin kutsujalleen.

Roskienkeruu huolehtii aikanaan poikkeuksen seurauksena vapautuneista olioista. Tämän lisäksi D takaa, että seuraavat ohjelman osat suoritetaan näkyvyysalueelta poistuttaessa:

7.3.2 scope[49]

Aiemmin esitetyillä finally-lohkoilla pystytään käsittelemään tilanteet, joissa jokin koodinpätkä tulee suorittaa joka tapauksessa. Tästä seuraa kuitenkin helposti monimutkaista koodia. Esitetyssä esimerkkifunktiossa on vain kaksi finally-lohkoa, mutta siltikin virheenhallintakoodi hallitsee sitä.

Koska C++ -kieli on D-kielen esikuvana, ei ole yllättävää, että RAII-tekniikka (Resource Acquisition Is Initialization) on myös D-kielessä mukana. D-kielessä tekniikkaa voi käyttää kuten C++ -kielessä määrittelemällä uuden struct-luokan, joka konstruktorissa varaa resurssin ja destruktorissa vapauttaa sen.

Uuden tyypin luominen yksittäistä tilannetta varten on kuitenkin työlästä. D-kielen vastaus on scope-lause. Lauseella voidaan määritellä koodi, joka halutaan suoritettavaksi, kun nykyiseltä näkyvyysalueelta poistutaan. Seuraavassa aiempaa (lähes) vastaava esimerkki.

string getDataFromImaginaryDatabaseApiAndUseScope( )

{

    DataBase database = new DataBase( );

    scope(exit) database.close( ); // starts scope

    Query query = new Query("select * from table");

    scope(exit) query.close( ); // starts another scope

    // When returning the scopes are closed and their code handled in reverse

    // order: first query is closed, then database connection

    return query.execute( );

}

Poikkeamana esimerkkien toiminnallisuuden välillä on, ettei mahdollista poikkeusta oteta kiinni ja tulosteta. Tämä ei scope-lauseella olisikaan mahdollista, koska ne eivät tallenna poikkeusta.

On myös mahdollista käyttää scope-lauseita eri lailla riippuen näkyvyysalueelta palaamisen syystä. Aiemmin esitetty scope(exit)-lause käsittelee lohkonsa aina, scope(failure) vain jos poikkeus on tapahtunut ja scope(success) vain jos poikkeusta ei ole tapahtunut.

void transaction(Account payer, Account receiver, double amount)

{

    double payerOriginal = payer.getBalance( );

    double receiverOriginal = receiver.getBalance( );

    // If exception occurs later, this will ensure the account balances will

    // be rolled back to original situation.

    scope(failure)

    {

        payer.setBalance(payerOriginal);

        receiver.setBalance(receiverOriginal);

    }

    payer.withdraw(amount);

    receiver.save(amount);

}

7.3.3 Ohjelmointivirheet

D-kielessä on varsin vankka tuki ohjelmointivirheiden tarkistukseen. Kääntäjä tekee automaattisesti tarkistuksia, mikäli se pystyy. Esimerkkeinä kiinteän kokoisen taulukon indeksointi rajojen yli ja alustamattoman olio-muuttujan aiheuttaa käännösvirheen.

Tämän lisäksi ohjelmassa on paljon olettamuksia, joiden perusteella se on rakennettu: vuodessa on vain 12 kuukautta, veroja ei voi maksaa yli 100 %, tai "tämä funktio ei heitä poikkeuksia". Usein tällaisten olettamuksien rikkoontuminen on merkki ohjelmointivirheestä. Mikään yleiskäyttöinen ohjelmointikieli ei pysty tällaisia virheitä tarkistamaan ilman ohjelmoijan apua. D-kielen apukeinoja tähän ovat assert- ja static assert -lauseet.

7.3.4 assert ja static assert[50]

Sekä assert, että static assert toimivat samalla periaatteella. Niille annetaan ehto tarkistettavaksi. Jos ehto ei ole tosi, seuraa virhe. Erona on, että missä vaiheessa virhe havaitaan. Assert-lauseen virheet voidaan havaita vasta ajon aikana, static assert -lause huomaa virheet jo käännöksen aikana.

void assertExample(int[] table)

{

    assert (table.length < 128);

}

void staticAssertExample(int size)(int[size] table)

{

    static assert (size < 128);

}

7.3.5 Sopimuspohjainen ohjelmointi

Sopimuspohjainen ohjelmointi on ohjelmointitapa, joka esiteltiin kielen osana ensimmäistä kerta Eiffel-kielen yhteydessä.[51] Siinä koetetaan pienentää ohjelmointivirheiden riskiä määrittelemällä funktioille ja luokille sopimuksia. Sopimuksien noudattamista tarkkaillaan ajon aikana ja rikkomukset saavat aikaan suorituksen keskeytyksen. D-kielessä tämä tarkoittaa AssertError-olion heittämistä poikkeuksena.[52] Sopimuspohjaisen ohjelmoinnin etu on, että virheet huomataan nopeasti ja lähellä niiden alkusyytä.

7.3.5.1 Esi- ja jälkiehdot[53]

Funktioiden sopimuksen ehtoina on esiehdot ja jälkiehdot. Esiehdot määrittelevät funktion parametrien lailliset arvot. Jälkiehto määrittelee funktion paluuarvon lailliset arvot. Ehtoja käyttäessä ne kirjataan nimettyyn in- ja/tai out-lohkoon. Funktion normaali lohko erotetaan nimeämällä se body-nimellä.

int countTaxesToPay(int wages, int taxPercentage)

in

{

    // These checks will be made for the parameters each time function is called

    assert (wages >= 0);

    assert (taxPercentage >= 0);

    assert (taxPercentage <= 100);

}

out(result)

{

    // "result" is the returned value. It will be checked with the following

    // checks each time the function returns.

    assert (result >= 0);

    assert (result <= wages);

}

body

{

    // The actual implementation of the function

    return wages * taxPercentage / 100;

}

7.3.5.2 Invariantit[54]

Luokalla voi olla invariantteja, eli ehtoja joiden tulee pitää aina paikkansa. Näiden tarkistusta varten D-kielessä voi määritellä luokkaan invariant-funktion, jossa ehtojen tarkastukset suoritetaan. Funktiota kutsutaan konstruktori-metodin lopussa, destruktori-metodin alussa sekä jokaisen julkisen ei-staattisen metodin alussa ja lopussa. Yksityisten metodien yhteydessä funktiota ei kutsuta, koska nämä tyypillisesti ovat apumetodeja, joilla luokan tila saatetaan lailliseen kuntoon.

class ObjectOnCanvas

{

    int x;

    int y;

    // The implementation of the invariant checks

    invariant( )

    {

        assert (x >= 0);

        assert (y >= 0);

        assert (x < SIZE_OF_CANVAS);

        assert (y < SIZE_OF_CANVAS);

    }

    this(int x, int y)

    {

        this.x = x;

        this.y = y;

        // Invariant will be called at end of constuctor

    }

    // Destructor is not defined, but before the compiler-generated destructor is

    // called, the invariant will be called.

    public void move(int xToMove, int yToMove)

    {

        // Invariant is called before entering public method

        // ... Do the calculation here ...

        // Invariant is called before exiting public method

    }

}

7.3.6 Nopeus vai turvallisuus[55]

Sopimusten tarkastuksen tuoma turvallisuus ei tietenkään tule ilmaiseksi: suorituskyky kärsii niistä enemmän tai vähemmän. Ohjelmoija itse päättää kumpi on ohjelman käyttötarkoituksessa tärkeämpää: nopeus vai turvallisuus. Jokaisen D-kääntäjän tulee tarjota mahdollisuus valita käännetäänkö assert- ja sopimustarkastukset mukaan lopputuotteeseen.

7.4 Tyypillisiä ohjelma-arkkitehtuureja

Yhdeksi D-kielen tavoitteista listattiin useamman ohjelmointitavan tukeminen.[56] Esikuvana olleen C++-kielen tukemien imperatiivisen ohjelmoinnin, olio-ohjelmoinnin ja geneerisen ohjelmoinnin lisäksi D-kieli tukee funktionaalista ja rinnakkaista ohjelmointia. D on siis moniparadigma-kieli. Ohjelmoija voi valita tuetuista ohjelmointitavoista ongelmakenttään parhaiten soveltuvan. Paradigmoja pystyy myös käyttämään samassa ohjelmassa ja yhdistämään toisiinsa. Lopputuloksesta voi tulla enemmän kuin osiensa summa.

7.4.1 Imperatiivinen ohjelmointi

Imperatiivinen ohjelmointi lienee yleisimmin käytetty ja ohjelmoijille tutuin. Tuotettu ohjelma on sarja komentoja, jotka muuttavat ohjelman tilaa. Useimmiten komennot on jaoteltu funktioihin. Funktiot voivat vapaasti muuttaa ohjelman tilaa (globaaleja muuttujia).

D-kielen tuesta imperatiiviselle ohjelmoinnille on kerrottu enemmän luvussa "Kontrollin ja/tai laskennan ohjaus"

7.4.2 Olio-ohjelmointi

Olio-ohjelmoinnissa ohjelman koodi on jaettu luokkiin. Ohjelman tila on tallennettu luokista luotujen olioiden jäsenmuuttujiin. Ohjelman tilaa muutetaan kutsumalla olioiden jäsenmetodeja. Tosin on mahdollista tehdä jäsenmuuttujan julkisiksi, jolloin niihin pääsee suoraan käsiksi. Olio-ohjelmoinnista D-kielessä kerrotaan tarkemmin myöhemmin luvussa 8.

7.4.3 Geneerinen ohjelmointi

Geneerisessä ohjelmoinnissa algoritmeja ei kirjoiteta yhdelle tietylle tyypille. Sen sijaan niitä voidaan käyttää, mikäli annettu tyyppi toteuttaa tarvittavat operaatiot. D-kieli on staattisesti tyypitetty, joten tämä tarkistus tehdään käännöksen aikana.

Geneeristä ohjelmointia tehdään muiden ohjelmointitapojen ehdoilla. Funktio (imperatiivinen ja funktionaalinen ohjelmointi) tai luokka (olio-ohjelmointi) voidaan kirjoittaa sitomatta sitä tiettyyn tyyppiin. Tällöin geneerinen ohjelmointi astuu saman tien kuvaan.

Seuraavassa esimerkki geneerisestä funktiosta:

// Check if an item is in a container. The std.algorithm contains a much better

// version. This is just a simple example.

bool find(T, E)(T toSearch, E toFind)

{

    foreach (current; toSearch)

    {

        if (current == toSearch)

        {

            return true;

        }

    }

    return false;

}

Ja esimerkki geneerisestä luokasta:

// Class for reading medical measurements with pull model (query and then

// read result). Extracted from a longer example, which was getting too long.

// Two template types:

//  queryCreator : creates a query string according to some protocol

//  parser       : is able to parse string into an array of measurements

// This is kind of a c++ approach... Might be better to implement as delegates

// in D? In c++ that would be too hard.

class PulledMeasurements(QueryCreator, Parser)

{

    // The protocol implementing objects stored as member variables

    QueryCreator queryCreator;

    Parser parser;

    // Constructor takes protocol handler objects as template parameters

    this(QueryCreator queryCreator, Parser parser)

    {

        this.queryCreator = queryCreator;

        this.parser = parser;

    }

    // Communicates with the connection point

    Measurement[] getMeasurements(Connection connection)

    {

        connection.write(queryCreator( ));

        return parser(connection.read( ));

    }

}

7.4.4 Funktionaalinen ohjelmointi

Funktionaalisessa ohjelmoinnissa kirjoitetaan imperatiivisen ohjelmoinnin tapaan funktioita. Funktioita kirjoittaessa ei kuitenkaan pyritä määrittämään miten niiden pitäisi päästä lopputulokseensa, vaan mikä haluttu lopputulos on.

Funktionaalisessa ohjelmoinnissa koetetaan välttää ohjelman tilan muuttamista. Puhtaasti funktionaalisissa kielissä tämä on kokonaan estetty. D-kieli ottaa "käytännöllisemmän" kannan asiaan.[57] Funktion voi esitellä "puhtaasti" funktionaaliseksi pure-määreellä, mikäli se ei muuta funktion ulkopuolista tilaa. Funktion sisällä voi muuttaa muuttujien arvoja.

Funktionaalisuuden etuna on, että algoritmeja on helpompi ymmärtää ilman ohjelman tilan vaikutusten ymmärtämistä. Samaten pure-funktioita voi huoletta käyttää samanaikaisuuden toteutukseen, koska ne eivät jaa muuttuvaa tietoa.

7.4.5 Rinnakkainen ohjelmointi

D-kieleen on rakennettu suora tuki rinnakkaista ohjelmointi silmällä pitäen aktorimallin myötä. Tästä myöhemmin lisää luvussa "Rinnakkaisuuden tuki".

7.5 Rinnakkaisuuden tuki[58]

D kielessä on tuki perinteisille kriittisten ohjelmakoodien suojaamiseen lukoilla, jotta jokainen säie pystyy niitä turvallisesti suorittamaan. D:stä löytyy esimerkiksi javasta tuttu synkronointi (synchronized) funktioiden turvalliseen suorittamiseen eri säikeiden välillä. D kuitenkin painottaa rinnakaisuuden ongelmien hallinnassa muita menetelmiä kuten tiedon jakamisen kontrollointia. Tässä voidaan hyödyntää esimerkiksi avainsanaa immutable, jolla varmistetaan ettei muuttujan/tietorakenteen tilaa pääse yksikään säie muuttamaan. D:stä löytyy myös tiedon jakamisen eri säikeiden kesken mahdollistava shared -avainsana. Avainsanalla shared voidaan tehdä muuttuja jokaiselle säikeelle näkyväksi globaaliksi muuttujaksi. Normaaliin tapaan luodusta muuttujasta int perThread on jokaisella säikeellä oma kopio, mutta muuttuja shared int perProcess on käytössä jokaisella säikeellä samassa prosessissa.

7.5.1 Aktorimalli[59]

D:stä löytyy myös viestinvälitysjärjestelmä, joka mahdollistaa viestien välittämisen eri säikeiden välillä. Säikeet voivat siis lähettää (send) toisilleen viestejä ja vastaanottaa (receive) niitä. Säie luodaan spawn aliohjelmalla, joka palauttaa Tid olion, joka on kahva luotuun säikeeseen. Aliohjelmalle spawn annetaan parametrinä suoritettava funktio, ja argumentit funktiolle.

Alla olevassa esimerkissä lähetetään viestejä toiselle säikeellä ohjelman pääsäikeestä. Vastaanottaja tulostaa saamaansa viestit, paitsi jos sille lähetetään kahva toiseen säikeeseen, jolloin sille lähetetään takaisin viesti.

import std.concurrency, std.stdio;

void main(string[] args) {

      auto tid = spawn(&writer);

   

      tid.send(“Hello”);    // send string to tid

      tid.send(999);        // send int to tid

      tid.send(thisTid);    // send handle to this thread to tid

      receive(   // receive messaged send to main thread

        (string msg) { writefln(“Main thread received:”,msg);}

      );

}

void writer(){

   while (true) {

      receive(   // pattern matching

        (int msg) { writefln(“message: %s”,msg); },  // receive int

        (string msg) { writefln(“message:”,msg);},  // receive string

        (Tid tid) { tid.send(“Hello”); }   // send mesage back to sender

      );

   }

}

7.6 Valittujen ratkaisujen arviointia

Poikkeuksenhallintamekanismit ovat erityisen hyvin mietittyjä. Monista kielistä löytyvä try-finally-rakenne on kelpo, mutta vaatii enemmän koodia ja sisäkkäisiä lohkorakenteita. RAII:n käyttö scope-lauseen avulla on tyylikäs ratkaisu.

Sopimuspohjaisen ohjelmoinnin tuki itse kielessä on miellyttävä lisä. Sen käyttäminen on kuitenkin ehkä hieman monimutkaisempaa kuin se joissain muissa kielissä on. Erilliset lohkot antavat kyllä enemmän vapauksia sopimusten tarkistamiseen. Tuntuma kuitenkin on, että parametrien tarkistukseen siinä joutuu kirjoittamaan "muutaman hassun merkin liikaa".

Moniparadigmamalli tekee kielen monimutkaisemmaksi ja vaikeammaksi omaksua. Mikäli ohjelmoijalla on kokemusta vain yhdestä ohjelmointitavasta, on vaikeaa ylläpitää koodia, joka on kirjoitettu jollain muulla tavalla. Toisaalta D-kieli ei pakota mihinkään tiettyyn ohjelmointitapaan. Ohjelmoija voi tutustua kieleen itselleen tutun paradigman kautta ja laajentaa työkalupakkiaan kokemuksen myötä. Tämä voi olla opettaisempaa kuin elää ja kuolla yhden ohjelmointitavan mukaan. D-kieli on suunniteltu siten, että ohjelmointitapoja voi käyttää yhdessä. Mikäli ohjelmoija onnistuu tässä, hän pystyy yhdistämään tapojen hyvät puolet. Lopputuloksesta voi tulla enemmän kuin osiensa summa.

Rinnakkaisuuden hallintaan tarkoitettu viestinvälitys mekanismi on yleistymässä modernien kielten keskuudessa ja tarjoaa toimivan työkalun rinnakkaisuuden hallintaan myös D kielessä.

8 Datan kapselointi

Olio-ohjelmoinille tyypillistä on tiedon ja toimintojen kokoaminen kokonaisuudeksi eli  kapselointi. D:ssä on myös muiden olio-ohjelmointiin tarkoitetujen kielten tapaan mekanismeja tiedon kapselointiin, kuten luokat, tietueet ja paketointi, jotka esitellään tässä luvussa.

8.1 Pistetäänkö pakettiin?

Yksi erityinen miellyttävä parannus verrattuna C++ -kieleen on, että D:ssä on mukana paketointimekanismi. Tämä mahdollistaa helpon tavan kapseloida koodia ja dataa. Samaten se myös mahdollistaa miellyttävän nopeat käännösajat.

D-kieleen valittu mekanismi muistuttaa varsin paljon python kielen vastaavaa. Se perustuu rakenteeseen, jolla tiedostot on tallennettu.[60] D-tiedosto muodostaa moduulin, joka on nimetty tiedoston mukaan. Samassa hakemistossa sijaitsevat moduulit muodostavat puolestaan paketin, joka on nimetty hakemiston mukaan.

Ratkaisusta seuraa rajoite, että d-tiedostot ja hakemistot tulisi nimetä d-kielen tunnusten nimeämissääntöjen mukaan. Nimet eivät esimerkiksi saa alkaa numeroilla tai sisältää väliviivoja. Lisäksi käytäntönä on kirjoittaa tiedostot ja hakemistot pienillä kirjaimilla, jotta siirrettävyys eri käyttöjärjestelmien välillä ei häiriinny.

Otetaan esimerkiksi seuraavanlainen hakemistorakenne:

rakenteen juuri

 |

 +-- main.d

 |

 +-- xml

 |    |

 |    +-- parser.d

 |    +-- writer.d

 |

 |-- json

      |

      +-- parser.d

      +-- writer.d

Rakenteesta muodostuisi moduulit main, xml.parser, xml.writer, json.parser ja json.writer.

8.1.1 Moduulien käyttö toisista moduuleista[61]

Moduuleja tuodaan toisiin moduuleihin import -esittelyn avulla. Esittelyssä annetaan aina koko paketin nimi, vaikka tuotava paketti olisikin samassa pakettihierarkiassa. Pakettirakenteen juuri (tai juuret) annetaan kääntäjälle komentoriviparametreina. D:n standardikirjasto tulee mukaan implisiittisesti.[62]

Esimerkiksi edellisen kappaleen main.d voisi sisältää seuraavat import-rivit.

import xml.parser;

import xml.writer;

import json.parser;

import json.writer;

Esittelyt tuovat näkyviin kaikki julkiset tunnukset main moduuliin niin kuin ne olisi esitelty moduulissa.[63] Tunnuksia pystytään käyttämään ilman pakettinimeä, mikäli ei ole epäselvyyttä mihin tunnukseen viitataan. Edes samojen funktionimien käytöstä ei välttämättä tule ongelmaa. Kutsu onnistuu, mikäli kääntäjä pystyy yksiselitteisesti valitsemaan yhden funktion saman nimisten kuormitettujen funktioiden joukosta. Käännösvirhe tulisi esimerkiksi, mikäli yhdessä moduulissa olisi esitetty funktio int doTheCalculation(int input)ja toisessa funktio int doTheCalculation(long input). Tällöin numeerinen arvo olisi implisiittisesti muunnettavissa molempien funktioiden parametriksi. Kääntäjä ei voi turvallisesti tehdä päätöstä, joten käännös keskeytyy virheeseen.

8.1.1.1 public import[64]

Normaalisti moduuliin tuodut tunnukset eivät ole näkyvissä toisissa moduuleissa. Eli jos moduuliin A on tuotu moduuli B, eivät B:n tunnukset näy automaattisesti moduuleissa, jotka käyttävät moduulia A. Joskus tämä voi kuitenkin olla käytännöllistä. Tätä varten D-kielessä on olemassa public import -esittely.

// In Module implementation.d

void initialize( ) {}

void cleanUp( ) {}

// In Module interface.d

public import implementation;

// In Module main.d

void main( )

{

    initialize( );

    // ... do some useful stuff in between

    cleanUp( );

}

8.1.2 Moduuli-yhteenvedot[65]

Toisin kuin C++ -kielessä, D-kielessä ei tarvitse jakaa käännösyksikön osia erillisiin otsikko- ja lähdetiedostoihin. Mikäli lopputuotteena on kuitenkin jonkinlainen kirjasto, jonka toteutusyksityiskohtia ei haluta jakoon, tarvitaan kirjaston API:n esittelyt asiakkaiden käyttöön. Tähän tarkoitukseen voidaan käyttää moduuli-yhteenvetoja. Ne vastaavat C++ -kielen otsikkotiedostoja. Niitä ei tarvitse luoda käsin; kääntäjää voi pyytää generoimaan ne. On myös mahdollista luoda ja ylläpitää niitä käsin.

8.2 Luokat

Olio-ohjelmointikielten tapaan D:ssä luokat voivat periytyä toisesta luokasta sekä niitä voidaan rikastaa rajapinnoilla. D:n luokkahierarkian yiluokka on Object, josta jokainen luokka periytyy.[66] 

8.2.1 Varsinaiset luokat

Luokka luodaan D:ssä class avainasanalla ja luokat voivat koostua jäsenmuuttujista, jäsenfunktioista, konstruktoreista, destruktoreista ja yksikkötesteistä, joista lisää kappaleessa 9.1. Luokille voidaan myös määritellä invariantteja, joista kerrotaan luvussa 7.3.5.2.

Esimerkissä luodaan luokka Person, jolla on kaksi kentää name ja age ja yksityinen kenttä personId:

class Person {

   

    string name; // public field

    int age;     // public field

    private int personId;   // private field

   

}

Person person = new Person();   // creates instance of Person

person.name = “John”;

Luokan kenttien sekä metodien näkyvyyttä voi rajoittaa avainsanoilla protected ja private. Moduulin ulkopuolella vain aliluokat pystyvät näkemään protected-jäsenet. Nekään eivät näe luokan yksityisiä jäseniä. Monesta kielestä poiketen moduulin sisällä on vapaa pääsy luokan jäseniin näkyvyysmääreestä riippumatta.

Esimerkissä luodaan myös ilmentymä new avainsanalla luokasta Person. Luokan kenttiin päästään käsiksi . operaattorilla. D:ssä ole C++ kielestä tuttuja -> ja :: operaattoreita.

Kenttiin päästään käsiksi myös luokkien ominaisuudella tupleof, joka palauttaa luokan kentät, pois lukien yksityiset sekä yliluokan kentät.

Alla oleva esimerkki näyttää kuinka päästään käsiksi Person luokan kenttiin tupleof ominaisuudella:

Person person = new Person();

person.tupleof[0] = “John”;    // sets name

person.tupleof[1] = 56;        // sets age

foreach (x: person.tupleof)

   writefln(x);                // prints on separate lines: John 56

8.2.1.1 Konstruktori[67] ja destruktori[68]

Mikäli luokan kenttiä halutaan alustaa ilmentymän luonnin yhteydessä voidaan luokalle luoda konstruktori. Konstruktoreja voi  olla useita ja niillä voi olla eri määrä parametrejä. Konstruktorista voidaan myös kutsua luokan toista konstuktoria.

Alla olevassa esimerkissä luokalla Person on konstruktori, jonka avulla voidaan alustaa name ja age:

class Person {

   

    string name; // public field

    int age;     // public field

    private int personId;   // private field

    this(string name, int age) {      // constructor which can be used to give

       this.name = name;              // default values for name and age

            this.age = age;        

    }

    ~this() {}             // destructor

   

}

Person person = new Person(“John”,56);   // creates instance of Person

Yllä olevassa esimerkissä on myös luotu destruktori luokalle. Destruktori luodaan kirjoittamalla ~this ja roskienkeräjää kutsuu sitä, kun luokan ilmentymä tuhotaan. Luokan ilmentymä tuhotaan, kun siihen ei ole enää viittauksia tai kun on kutsuttu delete operaattoria.

Luokalla voi olla myös staattinen konstruktori ja destruktori sekä stattinen jaettu (shared) konstruktori ja destruktori. Staattinen konstruktori on tarkoitettu luokan staattisten muuttujien  alustamiseen, joiden alkuarvoa ei voi tietää käännösaikana. Stattinen konstruktori luodaan lisäämällä static avainsana konstruktorille static this. Staattinen destruktori luodaan lisäämällä static avainsana destruktorille static ~this().

Statttinen jaettu konstruktori on tarkoitettu luokan jaetun globaalien datan alustamiseen ja se luodaanlisäämällä staattiselle konstruktorille shared avainsana sahred static this. Vastaavasti jaettu staattinen konstruktori luodaan shared static ~this().

8.2.1.2 Scope luokka[69]

D:stä löytyy myös scope luokka, josta voidaan luoda ainoastaan lokaali ilmentymä funktiossa. Luokan ilmentymä tuhotaan kun funktiosta poistutaan, joko luonnollisesti tai poikkeuksen seurauksena. Scope luokka luodaan lisäämällä avainsana scope luokan määrityksen eteen. esim scope class person. Scope luokan perivästä luokasta tulee myös scope luokka.

 

8.2.2 Tietueet

D-kielessä on luokkien lisäksi tietueita (struct). Tietueet ovat luokkien kaltaisia, mutta niiden käyttötarkoitus on erilainen. Luokat noudattavat viitesemantiikkaa, kun tietueet puolestaan noudattavat arvosemantiikkaa.[70] Tietueet ovat siis pieniä, kopioituvia olioita, joiden elinkaari riippuu näkyvyysalueesta, eikä roskienkeruusta. Tietueiden oliot tallentuvat muistissa pinoon, kun taas luokkieon oliot tallentuvat kekoon.

Yksityiskohtaisemmin tietueet eroavat luokista seuraavasti:[71]

// An example of a struct

struct Point

{

public:

    // Constructor

    this(uint x, uint y)

    {

        setCoordinates(x, y);

    }

    // Getter method, const ensures object state is not changed within

    uint getX( ) const

    {

        return x;

    }

    uint getY( ) const

    {

        return y;

    }

    // Setter method, cannot be declared const

    void setCoordinates(uint x, uint y)

    {

        this.x = x;

        this.y = y;

    }

private:

    // Actual contents of the object

    uint x;

    uint y;

}

8.2.2.1 Tietueen luonti[72]

Tietueolio luodaan muistissa pinoon. Sitä ei voi luoda new-kutsulla. Sen sijaan olio luodaan kutsumalla konstruktoria tietueen nimellä.

auto leftTop = Point(0, 0);

Tietueolio luodaan, kun sen konstruktoria kutsutaan ja se elää kunnes sen hetkinen näkyvyysalue loppuu. Mikäli olio ollaan palauttamassa funktiosta, siitä tehdään kopio.

8.2.2.2 Tietueen kopiointi

Kun tietueolio kopioidaan, sen sisältö kopioidaan sellaisenaan uuteen olioon. Kopiointi tehdään pinnallisesti[73] (shallow), joten se toimii suoraan ainoastaan, mikäli olion jäsenet noudattavat arvosemantiikkaa. Numeeriset muuttujat ja staattiset taulukot kopioituvat ilman erityistoimia. Esimerkiksi dynaamisista taulukoista ja olioista kopioituvat vain viitteet. Tällöin tietueoliot jakavat muistia, eikä arvosemantiikka toteudu halutulla tavalla.

8.2.2.2.1 Postblit-konstruktori[74]

Pinnallisen kopioinnin ongelman voi ratkaista postblit-konstruktorilla (postblit on suoraan käännettynä "blit:in jälkeen", blit puolestaan on lyhennelmä sanoista "Block Transfer", mikä tarkoittaa muistin kopiointia). Sillä määritetään toimenpiteet, joita tarvitaan tietueolion kopioinnissa pelkän muistin kopioinnin lisäksi. Kääntäjä generoi kutsun postblit-konstruktoriin kopioinnin jälkeen. Kääntäjä generoi kutsun myös, mikäli tietueen sisäisille tieteille. Näin voidaan rakentaa toimiva arvosemantiikka olioille, jotka rakentuvat toisista olioista.

struct PostBlitExample

{

public:

    // Normal constructor, which creates a member array

    this(int size)

    {

        list = new int[size];

    }

    // Postblit constructor, which will be called after the raw memory copy

    // has been done.

    this(this)

    {

        // At this point the list is a reference to the list of the the

        // original object. We want to make a copy instead. The dup operation

        // of the arrays will provide that easily.

        list = list.dup;

    }

    void set(int index, int number)

    {

        list[index] = number;

    }

    int get(int index)

    {

        return list[index];

    }

private:

    int[] list;

}

8.2.3 Rajapinnat[75]

Olio-ohjelmoinnissa on usein tarve luoda luokkia, jotka ainoastaan esittelevät metodikutsut, joita aliluokkien tulee toteuttaa. Näitä luokkia kutsutaan rajapinnoiksi. C++ -kielessä ne ovat vain tilattomia luokkia, joiden kaikki metodit ovat puhtaan virtuaalisia. Monessa muussa tunnetummassa kielessä (kuten Java, C#) niille on oma tyyppinsä. Näin on myös D-kielessä.

D-kielessä voi luoda rajapintaluokan käyttämällä avainsanaa interface. Rajapinnassa esitellyt metodit ovat normaalisti abstrakteja, eikä niillä ole toteutusta. Tällä tavoin käytettynä ne ovat täsmälleen samanlaisia kuin esimerkiksi Javassa.

// An interface following the Java-tradition. Only declarations public

// abstract methods are contained.

interface Transaction

{

    void initialize( );

    void execute( );

    void uninitialize( );

}

Tämän lisäksi rajapinnassa voi toteuttaa metodeja, mikäli antaa niille final-määreen. Näitä metodien toteutuksia ei voi siis korvata aliluokissa.

Metoditoteutukset on sallittu rajapinnassa, jotta NVI-tekniikka (Non-Virtual Interface) tulisi käytännölliseksi.[76] NVI-tekniikka on Herb Sutterin[77] esittelemä tapa jakaa rajapinta kahteen käyttötarkoitukseen. Ensinnäkin esitellyt metodit muodostavat halutun julkisen rajapinnan käyttäjille. Toiseksi ne määrittelevät kohdat, joissa rajapintaa voidaan aliluokissa laajentaa. NVI-tekniikassa tämä erotus tehdään eksplisiittiseksi. Ne metodit, joita käyttäjien on tarkoitus käyttää, esitellään julkisina final-metodeina. Ne käyttävät yksityisiä metodeja, jotka on jätetty abstrakteiksi. Tämä perustuu template method -suunnittelumalliin[78]. Yksityiset metodit muodostavat rajapinnan toteuttajille; toteutuksen yksityiskohdat voidaan jakaa sopivan pieniin palasiin, joita voidaan korvata tarpeen mukaan. Julkiset metodit puolestaan muodostavat rajapinnan käyttäjille; heidän ei tarvitse tuntea toteutuksen yksityiskohtia, koska ne käsitellään oikein julkisissa metodeissa.

// Example of an interface following the NVI idiom

interface NviTransaction

{

    // The public method forms a template of the usage. As the method is

    // declared final, the method cannot be overridden. The incorrect usage

    // (e.g. calling doExecute before initialize is called) is not

    // impossible, but it becomes more difficult. Also the intention of the

    // interface designer is clear.

    final void execute( )

    {

        initialize( );

        doExecute( );

        uninitialize( );

    }

private:

    // The extension points for the subclasses

    void initialize( );

    void doExecute( );

    void uninitialize( );

}

Kuten muissa kielissä on totuttu, rajapinnat voivat periä toisia rajapintoja.

8.2.4 Operaattoreiden kuormitus

D-kielessä on mahdollista kuormittaa operaattoreita omille luokilleen. Tämä tekee mahdolliseksi esimerkiksi olioiden yhteenlaskun '+'-merkillä. Oikein valituissa käyttökohteissa operaattoreilla voidaan selkeyttää koodia merkittävästi. Alexandrescu esittää kirjassaan[79] havainnollistavan esimerkin, jossa vertaillaan matemaattista laskentaa operaattoreilla normaaleihin funktioihin verrattuna:

m = 3 / (1/x + 1/y + 1/z);

m = divide(3, add(add(divide(1, x), divide(1, y)), divide(1, z)));

Operaattorit esitellään normaaleina metodeina, mutta niiden signatuurin täytyy noudattaa D-kielen määrittelemiä sääntöjä. Esimerkiksi vertailuoperaattori tulee esitellä nimellä opEquals. Sen tulee ottaa yksi parametri ja palauttaa totuusarvo.

class Value

{

    this(int value)

    {

        this.value = value;

    }

    bool opEquals(int compareTo)

    {

        return value == compareTo;

    }

    int value;

}

Operaattoreiden käyttö voi toimia myös hämäävästi, mikäli käyttökohteita ei ole valittu huolellisesti. Kukaan ei estä ohjelmoijaa tekemään yhteenlaskuoperaatiossa, jotain aivan muuta, kuten vaikkapa vähennysoperaatiota. Tai määrittelemään jakolaskun tarkoittamaan tiedostopolkujen yhdistämistä[80]. Toinen hämäännyksen aiheuttaja voi olla vain osittainen operaatioiden tuki: esimerkiksi luokalle on toteutettu yhteen-, mutta ei vähennyslaskuoperaatio. Operaattoreiden kuormituksesta saa siis lisäarvoa vain, mikäli ohjelmoijalla on hyvä tuntuma API-suunnitteluun.

8.2.4.1 Dynaamisuutta staattisesti tyypitetyssä kielessä

Vaikka D on staattisesti tyypitetty, on siinä jonkin verran myös dynaamisen tyypityksen elementtejä. Yksi esimerkki on mahdollisuus kuormittaa metodin kutsumiseen käytetty operaattori, eli opDispatch. Operaattoria kutsutaan, mikäli kääntäjä ei löydä olion luokasta vaadittua metodia.[81] Tämän avulla ohjelmoija voi lisätä omaa logiikkaansa metodikutsuihin.

Seuraavassa esimerkissä luodaan geneerinen luokka Log, jonka käärii sisäänsä jonkin toisen olion. Koska Log-luokassa ei ole metodeja itsessään, kaikki metodikutsut menevät opDispatch-metodille. Se tulostaa metodin nimen ja parametrin arvot. Tämän jälkeen se ohjaa kutsun oikean olion oikealle metodille antaen sille alkuperäiset arvot.

import std.stdio;

import std.variant;

class Log(T)

{

    this(T realObject)

    {

        this.realObject = realObject;

    }

    auto opDispatch(string method, Args...)(Args arguments)

    {

        // first log the method and its parameters

        writeln(method, " with arguments ", arguments);

        // Then use mixin to generate code for forwarding method call to the

        // actual object.

        return mixin("realObject." ~ method ~ "(arguments)");

    }

    T realObject;

}

class A

{

    void method(int a){ }

}

void main( )

{

    auto a = new Log!A(new A);

    a.method(1234); // prints "method with arguments 1234"

}

Mahdollisuus kuormittaa funktionetsintäoperaatio on siinä mielessä mielenkiintoinen, että sillä voidaan toteuttaa logiikka, jossa luokan metodeja voidaan ajon aikana lisätä tai poistaa.[82] Vastaavanlaisia ominaisuuksia löytyy yleensä vain dynaamisesti tyypitetystä kielestä.

8.3 Perintä

Luokka voi D:ssä periytyä vain yhdestä luokasta ja luokkaa perii yliluokasta kentät ja funktiot. Kaikista luokista voi luoda aliluokan paitsi final luokista. Luokasta luodaan final luokka lisäämällä final avainsana luokan määrityksen eteen. Abstraktista  luokasta taas puolestaan ei voi luoda ilmentymää, ainoastaan luokasta peritylle alilokalle voidaan luoda ilmentymä.

Alla olevassa esimerkissä luokka student perii luokan person:

class Person {

   

    string name;                 // public field

    int age;                     // public field

    private int personId;        // private field

    this(string name, int age) { // constructor which can be used to give

       this.name = name;         // default values for name and age

            this.age = age;        

    }

    ~this() {}             // destructor (just an example, not doing anything)  

}

class Student :Person {

 private int studentId;

 this(string name, int age, int studentId) {   //constructor for student

   super(name, age);                           // calls constructor for

   this.studentId = studentId;                 //  superclass

 }

}

Student student = new Student(“John”,56,100); // creates instance of Student

Luokassa student on käytössä kaikki kentät ja funktiot, jotka on käytössä myös luokassa Person. D:ssä aliluokka näkee myös yliluokan suojatut(protected) kentät. Aliluokka voi korvata yliluokan funktioita nimeämällä funktion aliluokassa samalla nimelllä ja  antamalla saman määrän parametrejä.

8.3.1 Abstrakti luokka

Abstrakti luokka luodaan D:ssä lisäämällä avainsana abstract luokan määrityksen eteen. Abstrakti luokka voi sisältää normaaleja funktioita sekä abstrakteja funktioita, joille aliluokkien on annettava toteutus.

Alla olevassa esimerkissä abstraktissa luokassa A on määritelty funktio sayHello ja abstrakti funktio sayHelloTo, johon aliluokkien on annettava toteutus:

abstract class A {               // abstract class A

    void sayHello(){

        writefln("Hello!!");

    }    

    abstract void sayHelloTo(string name);          // abstract funtion

}

class B : A {

    // implementation for abstract function sayHelloTo

    void sayHelloTo(string name){               

        writefln("Hello %s!!",name);

    }

}

 A ex = new B();

 ex.sayHello();                    // prints "Hello!!"

 ex.sayHelloTo("Mike");   // prints "Hello Mike!!"

8.3.2 Moniperintä

D-kielessä sallitaan moniperintä siis ainoastaan rajapinta-luokista. Vastaavankaltaiseen toiminnallisuuteen voidaan päästä käyttämällä alias this -toiminnallisuutta sisäkkäisten luokkien kanssa. [83]

D-kielessä voidaan johtaa metodikutsut suoraan luokan jäsenellä alias this -toiminnallisuuden avulla. Luokan esittelyssä tämä kerrotaan yhdellä rivillä.[84]

import std.stdio;

class B

{

    void methodB( ) { }

}

class A

{

    B b;          // B is set as member of A

    alias b this; // Here we tie the types together with "alias this"

    this ( )

    {

        this.b = new B;

    }

    void methodA( ) { };

}

void main( )

{

    auto a = new A;

    a.methodA( );  // Calling own methods as normal

    a.methodA( );  // ...but can call B:s methods directly as well

    useB(a);       // ... and can pass A along as if it were B

}

void useB(B b)

{

    b.methodB( );

}

Tämä itsessään on jo kätevää. Tarvitaan kuitenkin vielä mahdollisuus korvata alkuperäistä toteutusta uudella. Tämä onnistuu perimällä yläluokka luokan sisällä, korvaamalla alkuperäinen toteutus ja määrittelemällä alias tämän sisäisen luokan tyyppiseen jäsenmuuttujaan. Aiemmasta muokattu esimerkki valaisee asiaa:

class B

{

    void methodB( ) { writeln("b"); }

}

class A

{

    // Nested class for replacing original funtionality

    private class OverriddenB : B

    {

        // Replacing original functionality as in normal inheritance

        void methodB( ) { writeln("overrridden b"); }

    }

    // Tying the nested class into this type

    OverriddenB b;

    alias b this;

    this ( )

    {

        this.b = new OverriddenB;

    }

    void methodA( ) { };

}

void main( )

{

    auto a = new A;

    a.methodB( ); // The call will be made to overridden implementation

}

Koska alias this-määrityksellä voi esittää niin monta kertaa kuin haluaa, sitä voidaan käyttää kuin moniperintää.

8.4 Arvio valituista ratkaisuista

Mikäli on taistellut C++ -kielen #include-direktiivien, hitaiden käännösaikojen, otsikkotiedostojen ja nimien etsintäsääntöjen kanssa, D-kielen paketti/moduuli -ratkaisu tuntuu suorastaan ihanalta. Vaikka vastaava ratkaisu onkin saatavilla myös muissa kielissä, ei se vähennä sen arvoa.

Luokkien jako arvo- ja viitesemantiikan omaaviin tekee kielen joustavammaksi. Viitesemanttisten luokkien käyttö on helpompaa, joten ohjelmoija voi oletusarvoisesti käyttää niitä. Mikäli suorituskyky kuitenkin vaati tai mikäli arvosemantiikka tuntuu luontevammalta, on mahdollista käyttää tietueita tarkoitukseen. On myös hyvä, että jako on tehty selväksi; luokat ja tietueet ovat erillisiä sekä erilaiset ominaisuudet omaavia tyyppejä. Eri semantiikojen välillä eksyminen ei ole niin helppoa kuin esimerkiksi C++ -kielessä.

Rajapintojen käyttö NVI-tekniikan mukaan on voimallinen ratkaisu. Hyvä API-suunnittelija voi käyttää sitä hyödykseen ja vähentää API:n käyttäjien sekä toteuttajien virheitä. Toisaalta voimallisista ratkaisuista seuraa myös enemmän vastuuta. Toteutukset rajapinnassa mahdollistavat moninperintään liittyvän ongelman, jossa saman niminen toteutus peritään useammalta rajapinnalta. Kumpaa kutsutaan? Tästä ei löytynyt selvää ohjeistusta Alexandrescun kirjasta tai D-kielen spesifikaatiosta. Ainakin dmd-kääntäjällä kutsu menee ensin perityn rajapinnan toteutukseen. Turvallisempi vaihtoehto olisi ollut antaa tilanteessa kääntäjävirhe. Asian kanssa voi kuitenkin elää. Jokaisessa kielessä on omat sudenkuoppansa, jotka tulee tietää.

Luokkien yksityisten jäsenten näkyminen kaikkialle moduulin sisällä on mielenkiintoinen ratkaisu. Toisaalta tämä voi lisätä ohjelmointivirheiden mahdollisuutta, koska dataan voi päästä suuremmalta näkyvyysalueelta. Toisaalta varsin usein noudatetaan käytäntöä, että samaan moduuliin laitetaan ainoastaan luokkia, jotka on suunniteltu olevan tiukasti kytköksissä toisiinsa. Tällöin ratkaisu saattaa vain helpottaa luokkien kanssakäymistä.

9 Kielestä löytyvät lisäominaisuudet

D-kielestä löytyy myös joukko mielenkiintoisia ominaisuuksia, jotka eivät luontaisesti kuulu edellä olleisiin lukuihin. Tässä luvussa niitä esitellään lyhyesti.

9.1 Yksikkötestaus

Ketterien menetelmien suosion myötä yksikkötestaus on tullut suositummaksi ohjelmistoprojekteissa. D-kielessä on suora tuki yksikkötestaukselle[85], tosin sen käyttö poikkeaa useammista kirjastopohjaisista ratkaisuista. Yksikkötestejä voidaan kirjoittaa unittest-lohkojen avulla. Lohkoihin voi kirjoittaa koodia ilman rajoituksia. Yksikkötestejä pystyy kirjoittamaan ilman kirjastotukea käyttämällä assert-tarkistuksia. Käännösvaiheessa voidaan päättää otetaanko käännösvaiheet mukaan käännökseen. Jos näin on, ne ajetaan ennen kuin main-funktion suoritus aloitetaan.

Ratkaisu on kevyt ja toimii. Joskin ei ehkä niin sujuvasti kuin joissain muissa kirjastopohjaisissa ratkaisuissa. On ainoastaan yksi assert-lause, jolla voidaan tarkistaa totuusarvoja. Yksikkötestauskirjastoissa on usein erilaisia funktioita, joilla voi verrata esimerkiksi verrata arvoja, poikkeuksien heittoa, ym. Näiden etuna on, että testiraporttiin saadaan tarkempi kuvaus viasta. Hieman ärsyttävää on myös, että yhden testin epäonnistuessa testaus lopetetaan saman tien. Tällöin myös virheraporttiin tulee pitkähkö listaus aktivaatiotietuepinossa olleista funktioista. Käytännöllisempää voisi olla ajaa kaikki testit läpi ja saada yksinkertaisempi raportti onnistuneista/epäonnistuneista testeistä.

Etuna yksikkötestien lisäämisestä kieleen on, että niitä voidaan käyttää ilman riippuvuuksia ulkopuolisiin kirjastoihin. Voi myös olla, että yksikkötestien helppous on voinut olla tavoitteena ratkaisussa, jossa luokkien yksityiset jäsenet ovat täysin saatavilla moduulin sisällä. Kenties tämän seurauksena yksikkötestien kirjoittaminen vaatisi vähemmän koodia pelkästään testejä varten? Tämän toteaminen vaatisi kuitenkin enemmän käytännön kokemuksia asiasta.

9.2 Sisäänrakennettu assembler

D-kielen yksi suunnitteluperiaatteista on mahdollistaa tiukka optimointi tarpeen vaatiessa. Kielessä on tämän vuoksi myös tuki lähdekoodin keskelle kirjoitettavalle assembler-koodille.[86] D-kääntäjä osaa itse käsitellä assembler-koodin, eikä siihen tarvita erillistä työkalua. Assembler-koodin voi kirjoittaa muun koodin keskelle asm-lohkon avulla.

9.3 Dokumentaatiokommentit[87]

Vastaavasti kuin java tukee javadoc-kommentteja, on D-kielessä tuki dokumentaatiokommenteille. Yksityiskohtiin menemättä, sillä voidaan dokumentoida koodi siten, että siitä voidaan generoida erillinen dokumentaatio, kuten esimerkiksi HTML-sivusto. Toteutus on tehty myös siinä mielessä miellyttäväksi, että useimmiten ei tarvita erillismerkeillä koodattuja tunnussanoja dokumentaation kirjoittamiseen. Tällöin dokumentaatio säilyy ihmissilmään luettavana niin koodin seassa, kuin itse generoidussa dokumentaatiossa.

9.4 Yhteensopivuus C ja C++ -kielten kanssa

D-kieli kilpailee samoilla markkinoilla C- ja C++ -kielten kanssa. Näille kielille on olemassa runsaasti valmiiksi kirjoitettuja komponentteja, jotka on testattu toimiviksi. Jotta D-kieli pystyisi saamaan suosiota C- ja C++ -kieliä käyttävien keskuudessa, tulee olla jonkinlainen ratkaisu näiden komponenttien käyttöön D-kielestä

C-kielen osalta ongelmaa ei juurikaan tule, koska kielet ovat riittävän lähellä toisiaan.[88] Jos D-kääntäjällä on tiedossa tietueiden tai funktioiden esittely, pystyy se käyttämään niitä suoraan. Esittely tulee tehdä extern (C) -lohkon sisällä. D-kieli käyttää pääasiallisesti roskienkeruuta muistinhallintaan, mikä ei tietenkään käy täysin yksiin C-kielen käsin tehtävän muistinhallinnan kanssa. Tästä ei tule kuitenkaan ongelmaa, koska D-kielen std.c.stdlib-kirjasto sallii pääsyn malloc- ja free-kutsuihin.

C++ -kielen kanssa yhteentoimivuus on hankalampaa. Siihen on kolme tapaa[89]:

  1. Luo C++ -koodille C-rajapinta ja käytä sitä D-koodista
  2. Luo C++-koodille COM-rajapinta ja käytä sitä D-koodista (toimii vain Windowsilla)
  3. Koeta tulla toimeen D-kielen rajoitetulla tuella C++ -rajapinnoilla. Esimerkiksi poikkeukset ja C++ -templatet eivät toimi.

9.5 SafeD

D-kieli koettaa kattaa varsin laajan kirjon erilaisia käyttötarkoituksia. Toisaalta sillä on tarkoitus pystyä kirjoittamaan hyvin optimoitua ja koodia suorituskykyistä koodia. Toisaalta se tukee roskienkeruuta ja muita ohjelmointivirheitä vähentäviä tekniikoita. Yleisesti ottaen D-kielessä on helppoa tehdä asiat turvallisella tavalla. Vaarallisempiin tekniikoihin (kuten muistin suora käsittely, taulukoiden raja-arvojen tarkistusten sivuuttaminen, ym.) voi siirtyä, mikäli niitä tarvitsee.

Enemmän turvallisuutta vaativissa käyttötarkoituksissa voi olla hyödyllistä rajoittua ainoastaan D-kielen turvallisemmaksi tiedettyihin osa-alueisiin. Tällainen turvalliseksi tiedetty D-kielen osajoukko kulkee nimellä SafeD[90]. Sitä pystyy käyttämään samoilla kehitystyövälineillä, kuten koko D-kieltä. Kääntäjälle vain kerrotaan, että turvattomat operaatiot tulee estää käännösvirheellä. Esimerkiksi dmd-kääntäjällä on -safe komentorivioptio, jota voi käyttää moduulikohtaisesti [91].

Ohjelmoija voi kirjoittaa valtaosan ohjelmasta SafeD-osajoukkoa käyttäen. Mikäli myöhemmin tulee tarvetta, hän voi tehostaa koodiaan poistamalla turvatarkistuksia ja/tai käyttämällä turvattomampia ohjelmointitekniikoita.

10 Yhteenveto

10.1 Käyttökohteet

D asettuu kilpailee kielenä esikuvansa, eli C++ -kielen kanssa. C++ on varsin monimutkainen kieli ja vaatii kuria sekä taitoa ohjelmoijalta. Tästä syystä monesti on järkevämpää käyttää korkeamman tason ohjelmointikieltä.

On kuitenkin olemassa syitä perustella C++ -kielen käyttöä. Tällaisia ovat esimerkiksi:

D pystyy kilpailemaan C++ -kielen kanssa näistä kolmessa ensimmäisessä. Käytännössä tämä voisi siis tarkoittaa peliohjelmointia, systeemiohjelmointia, sulautettujen järjestelmien ohjelmointia tai reaaliaikaohjelmointia.

Koska D-kieli pystyy (ainakin osittain) integroitumaan C- ja C++ -kielillä tehtyjen komponenttien kanssa, sitä voi käyttää myös jo olemassa olevien komponenttien yhdistämiseen. Mahdollisesti ajan kanssa vanhat komponentit voidaan tarpeen vaatiessa kirjoittaa uudelleen D-kielellä.

D-kielessä on hyvä tuki rinnakkaisuudelle, joten se on varteenotettava vaihtoehto, mikäli sovelluskohteen laskentaa kannattaa rinnakkaistaa.

Toki D on yleiskäyttöinen ohjelmointikieli, joten sillä voi ohjelmoida periaatteessa mitä hyvänsä. Kuinka kätevää tämä on muihin ohjelmointikieliin verrattuna, riippunee siitä onko ongelma-alueeseen jo toteutettu kirjastotukea. Esimerkiksi web-palvelun toteutus lienee helpompaa kielillä, joissa on valmiita kirjastoja tähän tarkoitukseen.

D-kielen toteutukset ovat kohtuullisen vakaita, mutta tiedossamme ei ole kovin suuria ohjelmistokokonaisuuksia, jotka olisi toteutettu D-kielellä. Siksi D-kielellä ei kannattane vielä toteuttaa kaikkein tiukimpia turvallisuuskriteerejä vaativia sovelluksia (esim. terveydenhuoltoon tai lentokoneen ohjaamiseen).

10.2 D-kielen vahvuudet ja heikkoudet

D on staattisesti tyypitetyksi kieleksi yllättävän dynaaminen. Mixin-toiminnallisuudella voidaan tehdä laajennuksia kieleen. Kuormittamalla opDispatch-metodin voidaan luokan metodeja ajonaikaisesti muuttaa. Alias this -ominaisuuden avulla luokkien ominaisuuksia pystytään yhdistämään ilman moninperintää. Tästä syntyy vaikutelma siitä, että staattisen tyypityksen turvallisuutta pystyy käyttämään hyväksi, mutta silti hyötymään tarpeen mukaan dynaamisen tyypityksen joustavuudesta. Varmuus (suuntaan tai toiseen) syntyisi vasta laajemman käyttökokemuksen myötä.

D-kielen tyyppijärjestelmä on vahva ja sitä pystyy käyttämään edukseen ohjelmointivirheiden todennäköisyyksien pienentämiseen. Sekä const- että immutable -määreet rajoittavat aluetta, jolla voidaan muuttaa ohjelman tilaa. Näistä erityisesti jälkimmäisessä on etua rinnakkaisuudessa. Tähän auttaa myös päätös tehdä tiedosta oletusarvoisesti säikeelle yksityistä.

Mahdolliseen suosioon ohjelmoijien keskuudessa voi myös vaikuttaa se, että D-kieli on ulkoisesti samankaltainen kuin monet suosituista ohjelmointikielistä: C++, Java ja C#.

D-kieli taipuu useampaan ohjelmointitapaan, mikä on vahvuus. Kieli tuntuu myös yhtenäiseltä käytetystä ohjelmointitavasta riippumatta. Tämä toivon mukaan pienentää riskiä ohjelmoijien jakaantumiseen leireihin, jotka eivät osaa kommunikoida toistensa kanssa. Näin on käynyt esim. C++ -kielen kanssa, jossa osa ohjelmoijista vannovat poikkeuksien, template-tyyppien ja muiden "modernien" ominaisuuksien nimeen. Toiset eivät puolestaan suostu koskemaan näihin. Näin eri tavoilla kirjoitettua koodia on vaikeaa yhdistää. Riski on olemassa myös D-kielessä.

On sekä vahvuus, että heikkous, että D-kielellä voidaan pyrkiä hyvin viilattuun tehokkuuteen. Tehokkuushakuisuus on yksi kielen suunnitelluista käyttötarkoituksista. Ohjelmoijilla tuntuu kuitenkin olevan monesti taipumusta antaa liikaa ja liian aikaisin painoarvoa tehokkuudelle.

Kielen laajuuden voidaan katsoa olevan myös heikkous. Vaikka ominaisuudet tuntuvatkin toimivan varsin hyvin yhteen, on varsin mahdollista, että pitkänkin kokemuksen jälkeen löytyy uusia käyttötapoja tai näiden yhdistelmiä. Esimerkiksi operaattoreiden kuormituksella (varsinkin opDispatch-operaattorilla) ja mixin toiminnallisuudella voi varmasti saada aikaan hyvin hämmentävää koodia.

D-kieli on siis varsin samankaltainen kuin C++: hyvin voimakas työkalu, mutta käyttäjällä on vastuu siitä, että käyttö on järkevää. Tämä pitää paikkansa, vaikka D on monin tavoin turvallisempi ja käytettävämpi kieli kuin C++.


[1]Hamilton (2008) : The A-Z of Programming Languages: D http://www.techworld.com.au/article/253741/a-z_programming_languages_d/ Haettu 2.4.2011

[2]D change log: http://www.digitalmars.com/d/1.0/changelog1.html Haettu 2.4.2011

[3]D change log: http://www.digitalmars.com/d/1.0/changelog2.html Haettu 2.4.2011

[4]Wikipedia: D (programming language): http://en.wikipedia.org/wiki/D_(programming_language) Haettu 2.4.2011

[5]TIOBE Programming community index March 2011: http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html Haettu 2.4.2011

[6]Intro - D Programming language: http://digitalmars.com/d/2.0/index.html Haettu 2.4.2011

[7] http://www.digitalmars.com/d/2.0/lex.html#identifier. Haettu 19.4.2011

[8]D2 Lexical - keywords: http://d.digitalmars.com/2.0/lex.html#keyword. Haettu 26.4.2011

[9]http://www.digitalmars.com/d/2.0/lex.html#tokens Haettu 20.3.2011

[10]http://www.digitalmars.com/d/2.0/lex.html#StringLiteral Haettu 20.3.2011

[11] http://www.digitalmars.com/d/2.0/lex.html#tokens. Haettu 20.3.2011

[12] http://www.digitalmars.com/d/2.0/lex.html#integerliteral. Haettu 20.3.2011

[13] http://www.digitalmars.com/d/2.0/lex.html#floatliteral. haettu 20.3.2011#

[14] http://www.digitalmars.com/d/2.0/statement.html#BlockStatement. Haettu 1.4.2011

[15] http://www.digitalmars.com/d/2.0/statement.html#ScopeStatement. Haettu 1.4.2011

[16]Functions - D programming language: http://digitalmars.com/d/2.0/function.html Haettu 2.4.2011

[17]Scott (2009, 154): Programming Language Pragmatics

[18]Alexandrescu (20110 59): The D Programming Language

[19]Alexandrescu (2010, 59): The D Programming Language

[20]Alexandrescu (2010, 67-68): The D Programming Language

[21]Alexandrescu (2010, 59): The D Programming Language

[22]Alexandrescu (2010, 71-72): The D Programming Language

[23]Alexandrescu (2010, 72-73): The D Programming Language

[24]Alexandrescu (2010, 73): The D Programming Language

[25]Alexandrescu (2010, 73): The D Programming Language

[26]Alexandrescu (2010, 74): The D Programming Language

[27]Alexandrescu (2010, 74-75): The D Programming Language

[28]Alexandrescu (2010, 75-76): The D Programming Language

[29]Alexandrescu (2010, 78): The D Programming Language

[30]Alexandrescu (2010, 12): The D Programming Language

[31]Alexandrescu (2010, 78-80): The D Programming Language

[32]Edsker Dijkstra (1968): Go To statement considered harmful

[33]"DMD doesn't give error when goto skips initialization " http://d.puremagic.com/issues/show_bug.cgi?id=4101 Haettu 2.4.2011.

[34]Alexandrescu (2010, 68-70): The D Programming Language

[35]Alexandrescu (2010, 47-48, 82-84): The D Programming Language

[36] http://en.wikipedia.org/wiki/D_%28programming_language%29. Haettu 3.4.2011

[37] http://www.digitalmars.com/d/2.0/type.html. Haettu 2.4.2011

[38] http://www.digitalmars.com/d/2.0/arrays.html. Haettu 3.4.2011

[39] http://www.digitalmars.com/d/2.0/hash-map.html. Haettu 3.4.2011

[40] http://www.digitalmars.com/d/2.0/struct.html. Haettu 3.4.2011

[41] http://www.digitalmars.com/d/2.0/declaration.html#alias. Haettu 3.4.2011

[42] http://www.digitalmars.com/d/2.0/enum.html. Haettu 3.4.2011

[43]Alexandrescu (2010, 297): The D Programming Language

[44]Alexandrescu (2010, 288): The D Programming Language

[45]Alexandrescu (2010, 289-290): The D Programming Language

[46] http://www.digitalmars.com/d/2.0/function.html Haettu 3.4.11

[47]http://www.digitalmars.com/d/2.0/function.html Haettu 4.4.11

[48]Alexandrescu (2010: 301-312): The D Programming Language

[49]Alexandrescu (2010, 84-88): The D Programming Language

[50]Alexandrescu (2010, 325-327): The D Programming Language

[51]Wikipedia: Design by contract™: http://en.wikipedia.org/wiki/Design_by_contract Haettu 7.4.2011

[52]Alexandrescu (2010, 314): The D Programming Language

[53]Alexandrescu (2010, 317-320): The D Programming Language

[54]Alexandrescu (2010, 321-324): The D Programming Language

[55]Alexandrescu (2010, 324-325): The D Programming Language

[56]D - Overview: http://www.digitalmars.com/d/2.0/overview.html Haettu 10.4.2011

[57]Alexandrescu (2010, 166-168): The D Programming Language

[58]http://www.informit.com/articles/article.aspx?p=1609144   Haettu 9.4.11

[59] http://www.digitalmars.com/d/2.0/phobos/std_concurrency.html Haettu 3.4.11

[60] Alexandrescu (2010, 337-338): The D Programming Language

[61]Alexandrescu (2010, 338-340): The D Programming Language

[62]Alexandrescu (2010, 341): The D Programming Language

[63]Alexandrescu (2010, 341-344): The D Programming Language

[64]Alexandrescu (2010, 344-345): The D Programming Language

[65]Alexandrescu (2010, 349-353): The D Programming Language

[66] http://d.digitalmars.com/2.0/class.html. Haettu 15.4.11

[67] http://www.digitalmars.com/d/2.0/class.html#constructors. Haettu 15.4.2011

[68] http://www.digitalmars.com/d/2.0/class.html#destructors. Haettu 15.4.2011

[69] http://www.digitalmars.com/d/2.0/class.html#auto. Haettu 15.4.2011

[70]Alexandrescu (2010, 240): The D Programming Language

[71]Alexandrescu (2010, 240-241): The D Programming Language

[72]Alexandrescu (2010, 243-244): The D Programming Language

[73]Alexandrescu (2010, 246): The D Programming Language

[74]Alexandrescu (2010, 247-249): The D Programming Language

[75]Alexandrescu (2010, 212-213): The D Programming Language

[76]Alexandrescu (2010, 213): The D Programming Language

[77]Herb Sutter (2000): Virtuality, http://www.gotw.ca/publications/mill18.htm, haettu 17.4.2011

[78]Gamma, Erich; Richard Helm, Ralph Johnson, and John Vlissides (1995, 325). Design Patterns: Elements of Reusable Object-Oriented Software

[79]Alexandrescu (2010, 366) : The D Programming Language

[80]boost::filesystem::path::operator/ http://live.boost.org/doc/libs/1_46_1/libs/filesystem/v3/doc/reference.html#path-non-member-functions. Haettu 26.4.2011

[81]Alexandrescu (2010, 384-385) : The D Programming Language

[82]Alexandrescu (2010, 386-387) : The D Programming Language

[83]Alexandrescu (2010, 386-387) : The D Programming Language

[84]D2 Classes - Alias This: http://www.digitalmars.com/d/2.0/class.html#AliasThis Haettu 27.4.2011

[85]D2 - Unit tests: http://digitalmars.com/d/2.0/unittest.html Haettu 19.4.2011

[86]Alexandrescu (2010, 89): The D Programming Language

[87]D2 - Embedded documentation: http://digitalmars.com/d/2.0/ddoc.html Haettu 19.4.2011

[88]D2 - Interfacing to C: http://digitalmars.com/d/2.0/interfaceToC.html Haettu 19.4.2011

[89]D2 - Interfacing to C++: http://digitalmars.com/d/2.0/cpp_interface.html Haettu 19.4.2011

[90]D2: SafeD: http://digitalmars.com/d/2.0/safed.html Haettu 19.4.2011

[91]D2: Memory Safe D Spec: http://www.digitalmars.com/d/2.0/memory-safe-d.html Haettu 19.4.2011