Podstawy działania

edytuj

Moduły są osobnymi plikami o rozszerzeniu .d, domyślnie wraz z kompilatorem D otrzymujemy duży zestaw modułów podzielonych na kategorie etc, std oraz core.

Importujemy je znanym już poleceniem import:

import nazwa_modulu;

D pozwala na tworzenie tylko jednego modułu na plik, oraz wymusza zmieszczenie modułu w jednym pliku, można więc (zgodnie z pierwszym akapitem) przyjąć że 1 moduł = 1 plik. Deklaracja modułu wygląda następująco:

module nazwa_modulu;

/* Kod modułu - klasy, metody itp. */

Przykładowym wykorzystaniem modułów może być oddzielenie implementacji stosu (niedostępnego domyślnie w D na dzień dzisiejszy, tj. 24 czerwiec 2016) od właściwego programu.

//stack.d
module stack;

class Stack(T)
{
private:
    T[] tab;
public:
    void push(T val)
    {
        tab ~= val;
    }
    void pop()
    {
        assert(!empty());
        tab.length--;
    }
    T top()
    {
        assert(!empty());
        return tab[tab.length - 1];
    }
    bool empty()
    {
        return tab.length == 0;
    }
}
//main.d
module main;

import std.stdio;
import stack;

int main(string[] args)
{
    auto s1 = new Stack!ulong;

    long sum = 0;

    for(int i = 0; i < 12; i++)
        s1.push(i * i);

    /* Inne operacje na stosie */

    while(!s1.empty())
    {
        sum += s1.top();
        s1.pop();
    }

    writeln("Suma elementów ze stosu: ", sum);

	return 0;
}

Rozwiązywanie konfliktów

edytuj

Język D nie udostępnia rozbudowanych przestrzeni nazw jak C++, co za tym idzie po zaimportowaniu 2 modułów definiujących tą samą metodę (itp.) wywołanie nie będzie jednoznaczne i kompilator poinformuje o konflikcie. By ujednoznacznić obiekt do którego się odwołujemy należy poprzedzić go nazwą modułu i kropką.

modul.funkcja();

Prosty przykład:

//add.d
module add;

int calculate(int a, int b)
{
    return a + b;
}
//sub.d
module sub;

int calculate(int a, int b)
{
    return a - b;
}
//main.d
module main;

import std.stdio;
import add;
import sub;

int main(string[] args)
{
    int a = 5, b = 3;
    writefln("a + b = %d\na - b = %d", add.calculate(a, b), sub.calculate(a, b));
	return 0;
}

Pakiety

edytuj

Dobrą praktyką jest utrzymywanie porządku w modułach z wykorzystaniem pakietów. Są to zwyczajne katalogi, nazwa folderu pełni funkcję nazwy pakietu a by dostać się do modułu należy poprzedzić go nazwą pakietu i kropką. Składnia:

import pakiet.modul;

Przykład zastosowania:

// matematyka/add.d
module add;

int calculate(int a, int b)
{
    return a + b;
}
// matematyka/sub.d
module sub;

int calculate(int a, int b)
{
    return a - b;
}
//main.d
module main;

import std.stdio;
import matematyka.add;
import matematyka.sub;

int main(string[] args)
{
    /* Wskaźniki na funkcje skracające zapis */
    auto add = &matematyka.add.calculate;
    auto sub = &matematyka.sub.calculate;

    int a = 5, b = 3;
    writefln("a + b = %d\na - b = %d", add(a, b), sub(a, b));
	return 0;
}

Importowanie pojedynczych klas / funkcji

edytuj

Jak zapewne zauważyłeś mimo sporych nakładów pracy może dojść do konfliktu nazw a wykorzystywanie konkretniejszych wersji (poprzedzonych nazwą modułu i ew. pakietu) lub ich aliasów nie zawsze jest wygodne. Można temu zapobiec korzystając z operatora : w kontekście modułu – dzięki niemu można zaimportować jedynie najpotrzebniejsze funkcjonalności.

import pakiet.modul : cos_potrzebnego;

Dobre praktyki

edytuj

Kilka rzeczy (niewymienionych wcześniej) umilających innym programistom korzystanie z naszych modułów i pakietów:

  • Tworzenie modułu o nazwie pakietu, importującego wszystkie moduły z danego pakietu.
  • Komentarze, nazwy klas, funkcji, metod itd. po angielsku.
  • Wszystkie nazwy zwięzłe i jednoznaczne, lepiej nie wzorować się na calculate z przykładów.
  • Zawieranie w jednym module jednej funkcjonalności.