Język programowania D - nowa nadzieja czy niewypał

0

Język programowania D jest statycznie typowanym, kompilowanym językiem wieloparadygmatowym. Pierwsza stabilna wersja pojawiła się w 2007 roku i od tego czasu struktura języka dość mocno ewoluowała. Najważniejsze cechy języka wyróżniające go głównie od C++:

  • prawdziwe moduły, nie ma podziału na nagłówki oraz pliki z implementacją, jednak istnieje możliwość zawarcia interface'u w osobnym pliku
  • brak wielodziedziczenia, zamiast tego mamy interface'y
  • statyczne ify, które są rozwijane w czasie kompilacji
  • samodzielne szablony, co w sumie pozwala na łatwe metaprogramowanie
template Factorial(ulong n) {
    static if(n <= 1)
        const Factorial = 1; // można zauważyć, że jeśli typ jest określony jako const to jest on określany na podstawie przypisanej wartości
    else
        const Factorial = n * Factorial!(n - 1);
}
  • szablony są określane poprzez !() co pozwala uniknąć problemów jakie są w C++ np. z X< 1>2 > x1. Jednak jeśli mamy tylko pojedynczy argument szablonu można to zapisać jako X!my_func x
  • ujednolicony zapis typów w postaci <typ> <nazwa> oraz brak wielowyrazowych typów jak unsigned int, zamiast tego mamy uint, np.
uint[] tab; // tablica liczb bez znaku
real pi = 3.14; // liczba typu rzeczywistego
MyClass my_instance; // nie tworzy jeszcze instancji, by ją stworzyć trzeba użyć new MyClass();
my_instance = new MyClass();
string str = "abba"; // string jest aliasem do immutable(char)[] przez co sama tablica jest zmienna, ale nie da się zmieniać poszczególnych znaków
char[] editable_str = str.dup; // edytowalna kopia
const E =2.7182818; // automatyczne określenie typu jako real
idouble imaginary_number = 7.4i; // wbudowane liczby urojone i zespolone
creal complex_number = 15.3 + 3.992i + imaginary_number;
  • silny podział na klasy i struktury
  • wbudowany Garbage Collector
  • możliwość leniwej ewaluacji argumentów, np.
void my_func(int i, lazy void x) {
    while(--i)
        x();
}

int x = 0;
my_func(3, writeln(x++));
  • wbudowane w rdzeń języka tablice asocjacyjne oraz wektory
int[string][] table;
table["abba"] = [ 1, 3, 6, 28, 46 ];
table["baba"] = [ 5, 8, 2849, 53 ];
table["abba"] ~= [ 2, 5, 7 ]; // operator ~ służy do konkatenacji tablic
  • programowanie kontraktowe.
pure int f(int x)
in {
    // sprawdzenie argumentów na wejściu
}
out {
    // sprawdzenie wyjścia
}
body {
    // ciało funkcji
}
  • unit testy zawarte w bloku unittest {}

Co sądzicie o tym wynalazku?

0

A szybsze to niż przynajmniej najnowsze JVMy?

Moim zdaniem prym wiodą języki zarządzane i nie ma się co temu dziwić. C/ C++/ Pascal/ itp są używane generalnie dlatego, aby:

  1. wyciągnąć te kilkadziesiąt procent więcej wydajności,
  2. być w pełni zgodnym z natywnymi API, tzn np w przypadku Javy nie da się wysłać normalnej tablicy po JNI bez narzutu spowodowanego wstrzymaniem GC albo przekopiowaniem tablicy do natywnego bufora,
  3. zredukować zajętość pamięci - konsole dla przykładu mają bardzo mało pamięci, więc tam pakowanie JVMa nie miałoby sensu,

brak wielodziedziczenia, zamiast tego mamy interface'y

Ależ teraz jest parcie na to jednodziedziczenie. Moim zdaniem wielodziedziczenie jest OK, o ile nie wielodziedziczymy stanu, a tylko implementacje metod. Dla przykładu w C# mamy Extension Methods (chyba dobrze napisałem), a w Javie 8 mają być Defender Methods (czyli coś a la Extension Methods, tylko można tam dziedziczyć implementacje, więc jest to rozwiązanie bardziej skalowalne).

Moim zdaniem wielodziedziczenie stanu w C++ to jedna skrajność, a zupełny brak wielodziedziczenia to druga skrajność.

0

A co z przeciążaniem i definiowaniem operatorów?

Wibowit napisał(a)

Moim zdaniem wielodziedziczenie jest OK, o ile nie wielodziedziczymy stanu, a tylko implementacje metod.

Moim zdaniem implementacje metod lepiej delegować.

0

Moim zdaniem implementacje metod lepiej delegować.

A to niby dlaczego? Delegowanie to dokładanie niepotrzebnego śmiecia do kodu, tak samo jak rozwlekłe gettery i settery w Javie. Zwłasza jeśli implementacje metod są krótkie, np na jedną linijkę (w jednej linijce w np Scali można naprawdę dużo zrobić).

Myślę sobie o języku w którym można by zrobić dynamiczne wstrzykiwanie implementacji (tzn w locie, nie na etapie kompilacji), coś na wzór:

SynchronizedSet synchronizedSetObject = nonSynchronizedSetObject with SetSynchronizationSupport

Obecnie można by coś takiego zasymulować właśnie za pomocą delegacji, ale wymaga to sporego nadmiarowego kodu jeśli symulujemy jednocześnie wielodziedziczenie.

0

Delegowanie, mimo dokładania kodu, daje dużo większą elastyczność niż dziedziczenie.

0

Ta elastyczność jest w małym stopniu wykorzystywana, a powoduje znaczne pogorszenie czytelności. Wg mnie elastyczność kosztem czytelności to zła droga.

Poza tym dynamiczne wstrzykiwanie implementacji nie byłoby praktycznie ani trochę mniej elastyczne niż delegowanie kodu.

0

Aaaaaa, zapomniałem. Zamiast wielodziedziczenia możemy mieć mixiny szablonów. Czyli dajmy na to:

template MyMixin() {
    void hello(string name) {
        writeln("Hello ", name, "!");
    }
}

class MyClass {
    mixin MyMixin!();
}
// ...
MyClass c = new MyClass();
c.hello();

Co daje efekt podobny do prywatnego dziedziczenia z C++.

Definiować własnych operatorów nie idzie, ale przeciążanie działa w ten sposób, że mamy funkcję opUnary oraz opBinary i one przechwytują wywołania operatorów:

class MyClass {
    MyClass opUnary(string op)()
    if (op == "!") {
        // implementacja operatora !
    }

    MyClass opBinary(string op)(MyClass c)
    if (op == "~") {
        // implementacja konkatenacji
    }

    int opCmp(MyClass c) {
        // operator porównania, zwraca -1 jeśli większe, 0 jeśli równe i 1 jeśli mniejsze
        // pozwala to na przeładowanie wszystkich 6 operatorów (==, <, >, !=, <=, >=) za jednym zamachem
    }
}

Oprócz tego delegaty i wskaźniki na funkcje mają wręcz debilnie proste typy (w przeciwieństwie do C++)

int function(int) my_func; // odpowiednik C++ int (*my_func)(int);
int delegate(int) my_delegate;
0

Te mixiny to po prostu moim zdaniem wielodziedziczenie (i to na sterydach bym powiedział), z tym, że przy niejednoznacznościach kod się po prostu nie kompiluje, tzn nie ma mechanizmu obsługiwania niejednoznaczności. A więc nie jest to wielodziedziczenie w ścisłym (tzn Wikipediowym) tego słowa znaczeniu.

W sumie zależy co rozumieć przez wielodziedziczenie. Powinni zrobić jakieś rozróżnienie pomiędzy wielodziedziczeniem, a "wieloimplementacjami", tzn między sytuacją kiedy niejednoznaczności są niedozwolone, a kiedy są. Defender methods z Javy 8 nie wprowadzą w takim razie do Javy wielodziedziczenia w ścisłym tego słowa znaczeniu, ani Extension Methods z C# też nie wprowadzają.

0
Wibowit napisał(a)

Ta elastyczność jest w małym stopniu wykorzystywana, a powoduje znaczne pogorszenie czytelności. Wg mnie elastyczność kosztem czytelności to zła droga.

W małym stopniu? Delegowanie zwiększa elastyczność i testowalność aplikacji za to zmniejsza błędogenność zmian w kodzie. Praktyczne programowanie obiektowe polega na delegowaniu i komponowaniu, a nie na dziedziczeniu. Nie widzę możliwości napisania przydatnego kodu opartego na dziedziczeniu zamiast delegacji.

Poza tym dynamiczne wstrzykiwanie implementacji nie byłoby praktycznie ani trochę mniej elastyczne niż delegowanie kodu.

Jeśli kod jest napisany obiektowo, tzn. oparty na interfejsach, a nie implementacjach, to nie ma raczej z tym problemu.

winerfresh napisał(a)

Definiować własnych operatorów nie idzie

No to trochę słabo.
A jak z wydajnością?

0

trait Ordered

Możliwość zmiksowania tego traitu do dowolnej klasy jest moim zdaniem bardzo wygodna. Na pewno tworzenie delegatów tutaj nie ma najmniejszego sensu.

Generalnie takie twoje gadanie jest identyczne z gadaniem twórców Javy i jej pierwszymi piewcami. Twierdzili oni, że za pomocą interfejsów da się elegancko załatwić wszystko co tylko możliwe. Tyle, że teraz nawet sam ojciec Javy chwali Scalę, która przecież ma wielodziedziczenie. Poza tym programowanie obiektowe nie wymusza pojedynczego dziedziczenia.

0

Język D w wersji 2.0 pozwala na przeciążanie operatorów, robi się to trochę inaczej niż w C++:
http://www.digitalmars.com/d/2.0/operatoroverloading.html
Własne może się da używając innych stringów. Może dodadzą.

Niegłupie by było wsparcie dla nagłówków i klas C++, ale pomarzyć można. Przydałyby się biblioteki pod ten język, bo sam podobnie jak c++ wiele nie oferuje, ale jest od niego o wiele wygodniejszy.

0

@Razi91 wsparcia dla nagłówków z C++ nigdy nie zrobią, tak samo jak nie ma wsparcia dla C. Nie da się iść do przodu cały czas trzymając się spódnicy mamusi co twórcy D zauważyli, dlatego zrezygnowali z tej "dogodności". Współpraca jest jedynie na poziomie binarnym co daje możliwość portowania bibliotek, ale trzeba jednak zrobić ten interface w D.
Własne operatory może i by się czasem przydały, ale to jest raczej domena języków funkcyjnych. Osobiście nie za bardzo odczuwam taką potrzebę by móc definiować własne operatory, choć patrząc na aktualny sposób definiowania operatorów, nie zdziwił bym się jak by taki feature powstał.

Porównanie szybkości (jakieś tam marne):
C++ http://ideone.com/G0lNO
D http://ideone.com/rg6lf (niestety nie da się użyć v1[i][] * v2[i][] z niewiadomych mi powodów, być może ze względu na to, że są to tablice statyczne :/)

0

Był by jakiś admin skłonny dodać ankietę do tematu?

0

Język D ma wiele ciekawych pomysłów, bardzo użytecznych, ale dla mnie ten język jest trochę niespójny.
Im dłużej go poznaję z takich opowieści tym bardziej przypomina mi on listę przedwyborczych obietnic a nie język.

Kilka z tych pomysłów powyżej można stosować w C++:

typedef unsigned int uint;

)

  • co do delegatów to w C++ też są już proste:
boost::function<int (const char*)> f = std::atoi; 

Ogólnie język nie jest zły, tylko jak wygląda jego integracja ze światem? Tzn: GUI, Win32 API, bazy danych, języki skryptowe (Python)?

0

Przede wszystkim typedef w C++ i w D to zupełnie inna liga. Do wszystkiego były biblioteki, ale niestety większość z nich została zawieszona lub porzucona, jednak zawsze istnieje możliwość użycia bibliotek C (choć jeśli chodzi o WinAPI to chyba std.c.windows zawiera już gotowe deklaracje).
@vpiotr masz rację, ale jednak by użyć delegata w taki sposób jak ty napisałeś potrzebna jest dodatkowa biblioteka a tu mamy wszystko w rdzeniu. A czemu niby lista obietnic przedwyborczych, większość z tego co jest opisane jest zaimplementowane i działa przewidywalnie i poprawnie. Osobiście uważam, że jedyną rzeczą w jakiej ustępuje C++ jest brak popularności.

1 użytkowników online, w tym zalogowanych: 0, gości: 1