Witamy w studiu.
Witam, witam.
Panie D, pańskie umiejętności wzbudzają coraz większe zainteresowanie światowej opinii publicznej. Jak pan reaguje na to, że wiele osób pragnie poznać pana bliżej?
Jestem z tego faktu niezmiernie zadowolony, aczkolwiek wiem, że czeka mnie przez to dużo pracy. Sam język nie wystarczy, by przyciągnąć rzesze światowych programistów. Na popularność ma także wpływ aktywna oraz rozbudowana scena, a także baza pomocy. Wiele z technologii zawdzięcza swój sukces właśnie dużej ilości kursów, artykułów oraz zewnętrznych bibliotek. Początkujący może z nich dowiedzieć się wiele o specyfice języka oraz jego praktycznym użyciu; jest to coś, co ciężko znaleźć w dokumentacji lub z niej wygrzebać.
Czy może pan podać jakiś przykład?
Praktyczne zilustrowanie przyda się niemal wszystkim elementom języka, nawet tak podstawowym, jak część pętli. Na szczęście dzięki podobieństwom do C oraz C++ nie jest to aż takie trudne. Za to w porównaniu z C++ należy przyzwyczaić się do zupełnie innego sposobu pracy z ciągami tekstowymi.
Na czym polega różnica? Nie mówię tu tylko o ujednoliceniu. Jak wiemy, pisząc w C++ w zasadzie musimy swobodnie żonglować znajomością klasy ANSI String, jednocześnie umiejąc wykonywać niskopoziomowe operacje na typie char*, czyli wskaźniku do tablicy znaków, gdyż zdecydowana większość zewnętrznych bibliotek korzysta właśnie z niego.
U mnie ciąg tekstowy również istnieje jawnie jako tablica znaków, z tą różnicą, że jest to tablica dynamiczna, która potrafi automatycznie dobrać odpowiedni rozmiar do zawartości - wygenerowaniem odpowiedniego kodu zajmuje się kompilator. Niesie to za sobą pewne implikacje. Przede wszystkim na ciągach tekstowych można wykonywać operacje w dokładnie identyczny sposób, jak na tablicach. Kopiowanie, wyciąganie podciągów, sprawdzanie długości - znajomość tablic załatwia to w zupełności. Kolejną ważną zmianą jest kodowanie znaków. W obecnych czasach coraz większą popularność zdobywa Unicode. W starszych językach funkcje biblioteki standardowej operują na ciągach składających się zawsze z jednobajtowych elementów i obsługa dłuższych systemów kodowań wymaga stosowania specjalnych nakładek lub zewnętrznych implementacji. W moim przypadku wygląda to zupełnie inaczej - Unicode jest domyślnym systemem kodowania, a w samym języku obecne są aż trzy typy znakowe: char (UTF-8), wchar (UTF-16) oraz dchar (UTF-32).
Unicode wykorzystuję już powszechnie przy tworzeniu stron internetowych i cieszę się, że wreszcie mam do dyspozycji kompilowany język również mający jego obsługę w standardzie. Jednak jak wygląda sprawa implementacji
Przy korzystaniu z unikodu trzeba pamiętać o jednej rzeczy. Przykładowo, typ char zajmuje zawsze 1 bajt, a tymczasem w UTF-8 jeden znak może rozciągać się na kilka bajtów, co jest oznaczane specjalną sekwencją binarną na jego początku. W tablicach D elementy mają stały rozmiar, czyli pisząc literkę ą, tak naprawdę zajmujemy dla niej dwa elementy tablicy. Oczywiście funkcje biblioteki standardowej znakomicie sobie z obsługą takiego znaku radzą, ale musimy uważać przy wykonywaniu wszelkich przycięć bezpośrednio. Załóżmy, że mamy tekst "Ala bierze kąpiel". Ma on 17 znaków, lecz w tablicy będą one zajmować 18 elementów. Trzynasty ze znaków, litera "ą" jest rozciągnięta na dwa bajty. Teraz za pomocą standardowej składni spróbujemy wyświetlić pierwsze 13 elementów tablicy:
writefln(tekst[0..13]);
Okazuje się, że to nie będzie możliwe. Znaki standardowego ASCII wyświetlą się bez trudu, lecz później ujrzymy przykry komunikat: "Error 4: invalid UTF-8 sequence". Przyczyną jest rozcięcie pojedynczego symbolu na dwie części, z których żadna nie może bez drugiej istnieć, ponieważ zawiera dodatkowe sekwencje binarne identyfikujące typ bajtu, a w przypadku pierwszej pozycji - także jego długość.
Czy zna pan jakieś inne miejsca, w których może objawiać się problem kodowania?
Co ciekawe, problemy z unikodem mogą pojawić się nawet wtedy, gdy formalnie z niego w ogóle nie korzystamy. Podam tutaj przykład, który zapewne pan świetnie kojarzy, ponieważ właśnie w pańskim kodzie go podpatrzyłem.
Czy ma pan na myśli ten projekcik prostego serwera FTP?
Tak, dokładnie o nim mówię. Zrealizował pan rzecz wielowątkowo. Po odebraniu połączenia program tworzył nowy wątek i przekierowywał tam dalszą obsługę transmisji. W wątku była pętla, która odbierała dane od klienta i reagowała na wydawane komendy. Wczytane polecenie było wczytywane do bufora o długości 1024 znaków i stamtąd przekazywane dalej. Parser komend jednak z niewiadomej przyczyny funkcjonował wadliwie: pierwsza komenda przyjmowana była poprawnie, lecz w drugiej w parametrze pojawiał się na końcu szereg dziwnych symboli. Z kolei próba wyświetlenia bufora w celach debugowych kończyła się wyżej wspomnianym komunikatem sygnalizującym rozcięcie sekwencji Unicode. Rozwiązanie okazało się dość nietypowe: przetworzywszy pojedynczą komendę, należało wypełnić cały bufor np. spacjami i wtedy wszystko zaczynało pracować prawidłowo.
Niestety nie udało mi się odkryć, co było przyczyną generowania takich danych. Biblioteka std.socket jest słabo udokumentowana i jej działania musiałem uczyć się przez inżynierię wsteczną, analizując znalezione w sieci kody.
Z projektem tym (a konkretniej z parserem komend) związany był jeszcze jeden problem. W protokole FTP autoryzacja składa się z dwóch części. Najpierw wysyła się login użytkownika, a później jego hasło. W związku z tym trzeba zapamiętać gdzieś wprowadzony uprzednio login. Próbował pan to zrobić najbardziej intuicyjnie, jak się dało, przepisując parametr do drugiej zmiennej:
user = parameter;
I działa się tu rzecz dziwna, ponieważ gdy nadeszła komenda PASS i została rozbita na składowe, wpisana wcześniej nazwa użytkownika była podmieniana wpisane właśnie hasło.
Rozwiązanie także okazało się interesujące. Kompilator podaną przed chwilą operację traktował jako przepisanie wskaźnika do ciągu, w związku z czym nadpisanie parametru automatycznie równało się modyfikacji zawartości pola user. Po odkryciu tego faktu, poradził pan sobie w następujący sposób:
user = "";
user ~= parameter;Innymi słowy, inicjujemy w polu user pusty ciąg, a następnie doklejamy zawartość parametru. Po takim zabiegu wszystko zaczęło prawidłowo funkcjonować i można było przystąpić do kolejnego etapu prac.
Dziękuję za tę interesującą rozmowę. Mam nadzieję, że niedługo znów się spotkamy.
Ja również. Pozdrawiam wszystkich programistów.







Napisał NuLL w środę, 31 października 2007 o 01:20
Gratuluje pomyslu na notke : ) Ja programowaniem aplikacyjnym sie nie interesuje. Z checia bym poczytal jak w D sie przeciaza operatory i implementuje 'przyjaciol' bo byly to jedne z rzeczy ktorego mnie zniechecaly do C++ - mam nadzieje ze zmienili to ...