Monthly archive for March, 2008

Przekierowanie standardowych strumieni

2008-03-21 10:33

Dawno, dawno temu miałem dość oryginalny pomysł na program użytkowy. Miała to być (o wiele) lepsza wersja konsoli Windows, której wyższość miała się objawiać w wyglądzie oraz możliwościach; pewne inspiracje czerpałem tutaj z terminali linuksowych oraz basha (nie, nie chodzi tu o serwis z cytatami z IRC-a ;P). Jak większość “genialnych” pomysłów, także i ten nie doczekał się realizacji. Przyczyną było to, iż nie za bardzo wówczas wiedziałem, jak można zapewnić, aby aplikacje konsolowe funkcjonowały w ramach projektowanego terminala tak, jak to robią w domyślnym oknie konsoli w Windows. Większość problemu rozbijała się o to, jak pobierać dane przez ów program produkowane i jak przekazywać do niego te wpisywane przez użytkownika.

Przekierowane standardowe wyjście
Konsolka mała i ciasna, ale własna

Jako że było to bardzo dawno temu, nie znałem wtedy jeszcze takich terminów jak ‘standardowe wejście/wyjście’, ‘proces potomny’ czy ‘pipe‘, które byłyby tutaj bardzo pomocne. Dowiedziałem się o nich więcej dopiero znacznie później, przy czym wiedza ta w większości odnosiła się do systemu zgoła innego niż Windows :)
Pomyślałem jednak, że do problemu można by wrócić – zwłaszcza, że pytanie o to, jak uruchomić program z przekierowanym wejściem i wyjściem, pojawia się stosunkowo często. Okazało się, że nie jest to specjalnie trudne i sprowadza się właściwie do trzech kroków – z których tylko pierwszy może być nieco zakręcony. Cała procedura (w Windows) może wyglądać na przykład tak:

  1. Tworzymy dwie lub trzy jednokierunkowe rury (pipe, tworzone funkcją CreatePipe), które posłużą nam do odbierania wyjścia od i dostarczania wejścia do procesu potomnego. Należy przy tym zwrócić uwagę na dwie rzeczy:
    • Kij ma dwa końce i rura też, więc trzeba uważać, aby właściwie je zidentyfikować. Jeden koniec każdego pipe‘a podamy do procesu potomnego, zaś drugi zachowamy dla siebie. Ważne jest, aby były to właściwe końce pod względem kierunku przepływu danych – i tak w przypadku przekierowywania standardowego wejścia, powinniśmy podać procesowi potomnemu uchwyt do czytania, zaś po stronie naszej aplikacji wykorzystywać uchwyt do pisania.
    • Powstałe uchwyty będziemy chcieli dziedziczyćw procesie potomnym, zaś Windows dopuszcza to tylko wtedy, kiedy wyraźnie taką chęć określimy przy tworzeniu danego uchwytu. Robi się to, wypełniając jedno pole struktury SECURITY_ATTRIBUTES (tak, tej którą w 95% przypadków się ignoruje) i przekazując ją do funkcji tworzącej pipe.
    • Oprócz standardowego wejścia (STDIN) i wyjścia (STDOUT), istnieje jeszcze standardowe wyjście błędów (STDERR), które również możemy chcieć przekierować. Stąd może być konieczny trzeci pipe, o takim samym przypisaniu końców jak ten od STDOUT.
  2. Tworzymy proces potomny (CreateProcess). Musimy mu podać właściwe końce rurek w strukturze STARTUPINFO oraz poinstruować Windows, by były one wykorzystywane (flaga STARTF_USESTDHANDLES). Ponadto musimy wskazać, że chcemy dziedziczyć uchwyty i że utworzony proces konsolowy nie powinien pokazywać okienka (CREATE_NO_WINDOW) – to już przekazujemy w parametrach CreateProcess.
  3. Obsługujemy wejście i wyjście procesu potomnego. Na to drugie (oraz na zakończenie procesu potomnego) możemy czekać przy pomocy funkcji w rodzaju WaitForMultipleObjects. Natomiast organizacja wejścia zależy już od naszej aplikacji. Warto pamiętać, że w standardowej konsoli jest ono buforowane wierszami, zatem i my powinniśmy wysyłać coś do procesu potomnego dopiero wtedy, gdy zbierzemy całą linijkę danych wejściowych.

Łatwe, prawda? ;-) W innych systemach operacyjnych robi się to minimalnie inaczej (pipe, fork, dup2, …), ale ogólna idea jest podobna. Jak widać, kilkoma małymi rurkami można zdziałać całkiem sporo :]

Tags: , , , ,
Author: Xion, posted under Programming » 1 comment

O testach jednostkowych

2008-03-19 22:06

Kod skompilowany nie musi być kodem działającym poprawnie – tę prawidłowość zna każdy programista. Z drugiej jednak strony dogłębne przetestowanie programu jako całości to proces żmudny, trudny, niepokojący i obarczony sporym ryzykiem niepowodzenia. A przy tym oczywiście konieczny. Można go jednak nieco usprawnić poprzez ograniczenie możliwości występowania błędów w niektórych częściach programu – takich, które wcześniej potraktujemy testami jednostkowymi (unit tests).

Ten rodzaj testów w skrócie polega na tym, aby każdy napisany element kodu – co zależnie od języka i sposobu programowania może oznaczać np. funkcję lub klasę – opatrzyć jedną lub kilkoma procedurami testującymi. Mają one na celu sprawdzanie, czy ów element robi dokładnie to, co założyliśmy, poprzez użycie go “na zewnątrz”, w innym kodzie. Innymi słowy, taka procedura testowa ma na celu uruchomienie funkcjonalności jakiejś klasy/funkcji/itp. i sprawdzenie, czy rezultaty jej działania są zgodne z oczekiwanymi.
Takie procedury można pisać po zakończeniu implementacji testowanego elementu. Niektóre metodyki tworzenia oprogramowania zalecają jednak, aby… najpierw pisać kod testowy, a dopiero potem ten właściwy! Wbrew pozorom ma to całkiem spory sens. Jeśli bowiem zaczniemy od kodu, który ma naszej klasy używać, to istnieje większa szansa, że od początku wyposażymy ją w interfejs, którego rzeczywiście da się używać bez większych problemów. Na koniec zaś będziemy dysponowali też przykładowym kodem, obrazującym sposób korzystania z danej klasy, co zazwyczaj bywa przydatne.

Według mnie największą zaletą testów jednostkowych jest to, że przy pomocy odpowiednich narzędzi możemy je bardzo szybko napisać w postaci nadającej się do natychmiastowego uruchomienia. Brak konieczności tworzenia nowej aplikacji – dla samych tylko testów – jest niezwykle wygodny. Najczęściej bowiem jedyne, co musimy zrobić, to napisać klasę z procedurami testowymi i oznaczyć ją odpowiednio w kodzie, a następnie po prostu uruchomić testy (co często daje się zautomatyzować jako czynność następną po kompilacji) i obserwować wyniki.
Szczegóły mogą się aczkolwiek różnić w zależności od języka, jako że frameworków dla testów jednostkowych jest sporo. Mamy na przykład:

  • JUnit, przeznaczony dla Javy. To również pierwszy szeroko znany mechanizm tego typu i wzór dla licznych naśladowców (również pod względem nazewnictwa :-]).
  • NUnit to z kolei narzędzie dla .NET. Zasadniczo działa on bardzo podobnie do powyższego, również pod względem jego praktycznego użycia.
  • CppUnit to wreszcie próba przystosowania xUnit do języka C++. Wyszła ona całkiem zadowalająca, oczywiście zważywszy na to, co ów język oferuje chociażby w zakresie tak przydatnych tutaj refleksji (czyli mniej więcej tyle, co nic). Na pewno warto się tej bibliotece przyjrzeć.

Tak naprawdę to właściwie dla każdego liczącego się języka istnieje narzędzie tego typu. Polecam więc przyjrzenie się im (i całemu zagadnieniu testów jednostkowych), jeśli wcześniej nie mieliśmy z tym tematem styczności. Bo przecież testowania nigdy dość ;)

Tags: ,
Author: Xion, posted under Programming » 1 comment

Forumowa numerologia

2008-03-18 11:53

Internetowe fora to podobno następny stopień ewolucji list i grup dyskusyjnych Usenetu. Na pewno mają niezaprzeczalne zalety – jak choćby to, że można się do nich dostać przy pomocy zwykłej przeglądarki (nie potrzeba specjalnych klientów), a ich zawartość jest indeksowana przez co sprytniejsze wyszukiwarki. Ale razem z tymi zaletami “w pakiecie” mają też pewną cechę, którą osobiście uważam za wadę – wprawdzie niezbyt ciężką gatunkowo, lecz stanowiącą według mnie pewien mankament.
Mam tu na myśli panujący na forach kult numerków, którego najważniejszym obiektem jest oczywiście licznik postów opublikowanych przez danego użytkownika. Liczba ta, niby ważna dana osobowa, prezentowana jest prawie zawsze po lewej stronie każdej wypowiedzi (w miejscu, które żartobliwie nazywam ‘nagrobkiem’ :]).

Nagrobek forumowego postaCo w niej takiego złego? Obiektywnie patrząc, to tylko statystka, która nie niesie żadnej znaczącej informacji. Nie mówi ani o stażu danego delikwenta na forum, ani o jego codziennej aktywności, ani o jego poziomie zaawansowania w temacie, którego forum dotyczy, ani o umiejętności stosowania przezeń zasad netykiety. Część z tych informacji można odnaleźć, przyglądając się profilowi ze szczegółowymi informacjami o danym użytkowniku. Jednak one nie są pokazywane przy każdym poście – w przeciwieństwie do wspomnianego licznika postów. Czym więc on jest? Otóż jest to taki “magiczny numerek”, kojarzący się nieodparcie z dziesiątkami liczb występujących w grach (MMO)RPG i których mozolne podbijanie zajmuje wiele godzin. Prawie nic nie znacząca wartość, która może być co najwyżej skorelowana z samooceną tego, który zwraca na nią uwagę. Notabene zdarzało mi się widzieć rozwinięcie owego numerka do karykaturalnej postaci RPG-owej: z paskami życia, many i doświadczenia! Na całe szczęście na warsztatowe forum takie wynalazki (jeszcze) nie dotarły :)
Ażeby dodać przeciwwagę dla licznika postów (podbijanego zarówno przez wartościowe, jak i bezsensowne wypowiedzi), na wielu forach – włączając Warsztat – istnieje karma (choć są warianty tego mechanizmu o innych nazwach). W założeniu każdy użytkownik forum może innemu użytkownik dodać jeden punkt owej karmy, jeśli uzna jakąś jego wypowiedź za pomocną, interesującą lub wartościową. Akt ten nazywa się ‘dokarmianiem’, chociaż użyta w tym znaczeniu nazwa ‘karma’ nie ma rzecz jasna nic wspólnego z jedzeniem :) Tak czy owak, sumaryczna wartość zebranej przez forumowicza karmy jest, podobnie jak licznik postów, wyświetlana obok każdej jego wypowiedzi.

Pomysł na pierwszy rzut oka wydaje się znacznie lepszy niż licznik postów, ale ja uważam, że skutki istnienia obu tych mechanizmów są w gruncie rzeczy podobne. Oba są związane z jakimiś rzekomo znaczącymi liczbami, które mają w jakiś sposób opisywać użytkownika, którego się tyczą – co w domyśle znaczy oczywiście, że im większe one są, tym forumowa twórczość danej osoby jest bardziej wartościowa. Oba są też bardzo narzucające się innym użytkownikom; w końcu nie sposób przeoczyć czegoś, co jest wyświetlane obok każdej wypowiedzi.
I oba mogą sprawić, że będziemy – choćby podświadomie – zwracali uwagę na to, aby “przy okazji” dyskusji na forum podbijać te współczynniki jak najwyżej. Samo to nie będzie dobrze jej służyło, a poziom i przydatność forum może wręcz na tym ucierpieć. Zwiększenie licznika postów polega przecież na, niespodzianka, pisaniu postów – a wiadomo przecież, ze ilość nie musi przekładać się bezpośrednio na jakość. Z kolei polowanie na każdy dodatkowy punkt karmy oznacza dopisywanie swoich pięciu centów do każdego pojawiającego się wątku – zwłaszcza takiego z pytaniem lub prośbą o pomoc – nawet jeśli nie mamy w temacie zbyt wiele do powiedzenia (co może wręcz ograniczać się do zakamuflowanego powtarzania wypowiedzi przedmówców). Trudno się nie zgodzić, że podobne praktyki nie służą dobrze funkcjonowaniu forum jako źródła wiedzy i miejsca do rzeczowej dyskusji oraz zadawania pytań i otrzymywania na nie pożytecznych odpowiedzi.

I dlatego przyznam się do radykalnego poglądu. Gdybym dysponował odpowiednią mocą sprawczą, czym prędzej pozbyłbym się tego rodzaju pseudostatystyk – a przynajmniej schował je głęboko i nie eksponował przy każdej okazji. Na forum wymowni powinni być ludzie, a nie liczby :P

Tags:
Author: Xion, posted under Internet, Thoughts » 3 comments

MRT i techniki typu deferred

2008-03-16 21:42

Zdążyliśmy się już przyzwyczaić do tego, że w programowaniu grafiki bardzo często wykorzystujemy zastane mechanizmy w zupełnie inny sposób niż ten formalnie założony. I tak: tekstury nie muszą być tylko bitmapami nakładanymi na geometrię, ich rzekome współrzędne mogą tak naprawdę przechowywać pozycję 3D lub normalne, a piksele nie muszą wcale zawierać danych o kolorach.
Analogicznie potok renderowania nie musi też produkować pikseli do wyświetlenia na ekranie! Zamiast tego możliwe jest, aby dokonywał on najpierw wyliczania pewnych właściwości materiałów dla każdego piksela na powierzchni ekranu i to właśnie te informacje zapisywał jako wynikowy “kolor” w teksturze typu Render Target. Później byłyby one wykorzystywane do obliczeń związanych np. z oświetleniem i cieniowaniem sceny. Zaletą takiego podejścia (znanego powszechnie jako Deferred Shading) jest między innymi oszczędzenie sobie wielokrotnego przetwarzania wierzchołków przez vertex shader, np. wtedy, gdy potrzebnych jest wiele przebiegów dla wielu świateł. Z drugiej strony w ten sposób znacznie bardziej obciąża się jednostki Pixel Shader, których liczba w starszych karta jest stała i niezbyt duża. Korzyścią jest jednak uproszczenie całego procesu renderowania, nawet jeśli z powyższego opisu nieszczególnie to wynika ;-)

Pozostaje jeszcze pewien drobny szkopuł. Otóż materiały mają wiele parametrów, które często są złożone, i nie da się ich wszystkich upakować w pojedynczym pikselu, składającym się jedynie z czterech wartości zmiennoprzecinkowych. Dlatego też stosuje się tutaj “wielokrotne cele renderowania” (Multiple Render Targets – MRT), a więc produkowanie przez potok więcej niż jednej wartości koloru naraz. Zwykle po prostu każdy parametr materiału jest zapisywany osobno, do innej powierzchni typu Render Target. Rozróżnienie odbywa się na poziomie pixel shadera. Zamiast zwracać jedną wartość o semantyce COLOR0 (która domyślnie trafia do bufora ramki, czyli – w przybliżeniu – na ekran), może on wypluć także COLOR1, COLOR2 i tak dalej:

Pixel shader dla MRT

Rezultaty te chcielibyśmy rzecz jasna odebrać i zachować, ale w DirectX wystarcza do tego zwykła metoda SetRenderTarget, której podajemy powierzchnię działającą jako Render Target o danym indeksie. Są tutaj oczywiście pewne obostrzenia (łącznie z tym najbardziej oczywistym – rozmiaru powierzchni).
Największym mankamentem jest jednak to, że jedynie stosunkowo nowe karty (np. nVidii od serii 6) obsługują MRT. Można to sprawdzić, czytając wartość pola NumSimultaneousRTs struktury D3DCAPS, które da nam – niezbyt imponującą, bo wynoszącą zwykle 4 lub 8 – maksymalną liczbę Render Targets podpiętych jednocześnie. W tak niewielkiej ilości (zwłaszcza, jeśli równa jest ona nawet mniej, czyli… 1 :]) może być niełatwo zmieścić wszystkie potrzebne informacje o materiałach.

Ale w programowaniu grafiki zwykle bywa tak, że najlepiej jest wybierać techniki, które jeszcze wydają się nowe. Wtedy bowiem jest całkiem prawdopodobne, że w chwili kończenia projektu wsparcie dla nich będzie już powszechne. Zważywszy na to, że w moim przypadku ciężko jest powiedzieć, kiedy pisanie właściwego potoku renderowania zdołam chociaż zacząć – o zakończeniu nie wspominając – techniki typu deferred zdają się być całkiem rozsądnym wyborem do rozważenia :)

Bity bez znaku

2008-03-14 22:58

Chociaż programowanie staje się coraz bardziej wysokopoziomowe, zdarza się, że trzeba zejść na niziny i wykonać kilka “staromodnych” operacji’. Dotyczy to na przykład działań na bitach, co jest zazwyczaj związane jest z koniecznością interpretacji różnych sposobów reprezentowania danych. Bardzo dobrym przykładem jest chociażby blokowanie i odczyt/zapis powierzchni (tekstur, bufora ramki, głębokości, itd.) w bibliotekach graficznych. Format pikseli może być bowiem taki, że dostanie się do informacji o kolorze będzie wtedy wymagało nieco zachodu, w tym także operacji na pojedynczych bitach.

Ale nie tym będę się zajmował, gdyż, jak wiadomo, jest to przecież proste ;-) Dzisiaj chciałem zwrócić uwagę na pewien niezwykle ważny szczegół związany z operacjami bitowymi, mogący powodować powstawanie błędów, które na pierwszy rzut oka wyglądają cokolwiek tajemniczo. Chodzi mianowicie o różnice między typami liczbowymi ze znakiem i bez znaku, kiedy chodzi właśnie o działania z użyciem operatorów bitowych.
Ogólna zasada brzmi, aby – jeśli to tylko możliwe – korzystać wtedy wyłącznie z typów liczbowych bez znaku. Mogłoby się wydawać, że to żadna różnica, skoro i tak nie interesują nas wartości zmiennych w interpretacji liczbowej, a jedynie “gołe” bity. Rzecz w tym, że dla kompilatora takie rozróżnienie zazwyczaj nie istnieje i poza nielicznymi wyjątkami (jak std::bitset w C++) należy stosować zmienne typu liczbowego, gdy operujemy na bitach.
Istnieją zaś przypadki, kiedy uznawanie jakiegoś ciągu bitów za liczbę ze znakiem powoduje zupełnie inne efekty niż interpretacja tego samego ciągu jako liczby bez znaku. Naturalnie cały czas mówimy o korzystaniu jedynie z operatorów bitowych! Co to za szczególne sytuacje?…

  • Jedna z nich dotyczy automatycznego rzutowania typów mniejszych na większe. Jeśli mamy krótki ciąg bitów, zapisany np. w zmiennej o rozmiarze 1 bajta, i chcemy go przepisać do zmiennej o większym rozmiarze, to cała operacja jest w miarę oczywista dla typów bez znaku. W nowym słowie stare bity zostaną umieszczone na najmniej znaczących pozycjach, zaś reszta zostanie wypełniona zerami.
    Natomiast dla typów ze znakiem przepisywanie dotyczyć będzie liczby reprezentowanej przez rzeczone bity, a nie ich samych. Jeśli więc mamy np. ciąg ośmiu jedynek, to przy standardowym sposobie zapisywania liczb całkowitych oznacza on po prostu -1. Po rozszerzeniu wartości -1 na 32 bity otrzymamy znów ciąg samych jedynek – lecz tym razem aż trzydziestu dwóch, bo taka jest 32-bitowa reprezentacja liczby -1. Dla typów bez znaku otrzymalibyśmy naturalnie ciąg 24 zer i 8 jedynek.
  • Druga uwaga odnosi się do operacji przesunięcia bitowego w prawo, które może mieć dwa warianty. Nazywają się one… przesunięciem ze znakiem i bez znaku, zaś różnica między nimi polega na tym, czym są wypełniane wolne miejsca pojawiające się z lewej strony przesuwanego słowa. W pierwszym przypadku jest to najbardziej znaczący bit (zwany często bitem znaku dla typów ze znakiem, nawet jeśli faktycznie nim nie jest). W drugim zaś lewa strona jest wypełniana po prostu zerami.
    Kłopot w tym, że nie zawsze wiadomo, którego rodzaju przesunięcia akurat użyjemy. W C++ na przykład zasadniczo zależy to od typu argumentów operatora >>, lecz z drugiej strony wcale nie jest powiedziane, że zawsze stosowany jest wariant bez znaku dla typów signed i bez znaku dla unsigned. Jest to zależne od kompilatora, choć rzecz jasna większość zachowuje się w tym względzie odpowiednio rozsądnie.

Tym niemniej nie powinno się na takiej dobroci polegać i należy, do celów działania na bitach, używać typów bez znaku. Inna sprawa, że niektóre języki programowania takich typów w ogóle nie mają i wtedy możemy napotkać (nie)wielki problem. Ale to już całkowicie odrębny temat do narzekania i poruszę go może przy innej okazji :)

Tags: ,
Author: Xion, posted under Programming » 3 comments

Tajemnice stałych referencji

2008-03-13 21:47

Zapewne wielu programistów C++ zetknęło się z taką lub podobną sytuacją. Oto w pocie czoła napisana funkcja w postaci podobnej do tej:

  1. void Foo(string s) { /* ... */ }

zostaje obejrzana przez bardziej doświadczonego kodera, który stwierdza: “Powinieneś użyć tutaj stałej referencji jako parametru – czyli napisać:

  1. void Foo(const string& s)

Unikniesz wtedy niepotrzebnego kopiowania stringa”. Wówczas możemy zrobić wielkie oczy i zdziwić się bardzo, zwłaszcza jeśli wiemy co nieco o referencjach w C++. Mimo to sugestia ta jest jak najbardziej na miejscu i powinniśmy się do niej stosować. Wstyd przyznać się, że przez dłuższy czas niesłusznie brałem ją tylko na wiarę, lecz na szczęście jakiś czas temu dowiedziałem się dokładnie, o co tutaj chodzi. Tą cenną wiedzą oczywiście się podzielę :)

Wiadomo, że w C++ referencje to w zasadzie to samo, co (raczej rzadko używane) stałe wskaźniki – czyli zmienne w typu T* const. Różnią się one aczkolwiek składnią: wszystkie dereferencje są dokonywane przezroczyście i kod wygląda tak, jakbyśmy posługiwali się zwykła zmienną docelowego typu, na który referencja wskazuje (czyli T). Podobnie stałe referencje są odpowiednikami stałych wskaźników na stałe (const T* const), czyli takich, które nie pozwalają ani na modyfikację obiektu wskazywanego, ani na zmianę samego wskazania.
W każdym przypadku wskaźniki muszą jednak na coś pokazywać; na coś, co ma określone miejsce w pamięci, czyli adres. W zasadzie podobna reguła dotyczy też referencji – z jednym małym, ale jakże ważnym wyjątkiem.

Otóż powyższą funkcję (w wersji ze stałą referencją jako parametrem) możemy bez problemów wywołać tak, jak poniżej:

  1. Foo ("Hello world");

Niby nic nadzwyczajnego, ale zauważmy, że tworzony jest tutaj tymczasowy obiekt string, na który następnie pokazuje referencja w ciele naszej funkcji. Błąd standardu lub kompilatora? Wręcz przeciwnie – it’s not a bug, it’s a feature :)
Po prostu w C++ stałe (i tylko stałe) referencje mogą poprawnie wskazywać na obiekty tymczasowe. Życie takich obiektów jest wówczas przedłużane aż do czasu wyjścia poza zasięg istnienia referencji. W naszym przypadku utworzony tymczasowy obiekt string będzie więc dostępny w całej funkcji.

Biorąc pod uwagę fakt, że zamiana deklaracji string s na const string& s nie kosztuje nas nic (wewnątrz funkcji do parametru odwołujemy się tak samo), możemy zerowym kosztem zyskać sporą optymalizację. Przekazanie referencji kosztuje przecież tyle samo co przekazanie wskaźnika i na pewno jest nieporównywalnie tańsze niż wykonywanie kopii całego napisu.
Dlatego też nie tylko w przypadku klasy string, ale i we wszystkich podobnych sytuacjach obiekty powinniśmy przekazywać właśnie przez stałe referencje.

Tags: , ,
Author: Xion, posted under Programming » 4 comments

Języki całkiem naturalne

2008-03-13 10:48

Języki programowania są tworami sztucznymi, ale wykazują pewne podobieństwo do języków naturalnych – czyli tych, którymi posługują się ludzie. Mają na przykład swoje gramatyki, składnie i semantyki, chociaż pojęcia te rozumiemy oczywiście nieco inaczej. W obu typach języków można też wyrazić bardzo wiele różnych myśli i relacji, z tym że domena języków naturalnych jest rzecz jasna znacznie szersza.

Ponieważ jednak wszystkimi językami posługują się głównie ludzie, niektóre języki programowania dorobiły się pewnej “otoczki” podobnej do tej, jaka towarzyszy części języków naturalnych. Myślę nawet, że bazując na tym, można wyróżnić odpowiedniki między obiema grupami. Może nie zawsze będą one wystarczająco sugestywne, ale przecież nie zaszkodzi spróbować ;-)
Jak dotąd w przypływie niewytłumaczalnej kreatywności wymyśliłem takie oto pary:

  • Książka “Hello World”C++ ma to do siebie, że jako tako znają go prawie wszyscy, lecz poziom tej znajomości pozostawia zwykle sporo do życzenia. Tak więc mimo tego, iż należy on do najpopularniejszych języków programowania, osób posługujących się nim zupełnie poprawnie nie ma znowu aż tak dużo. Zaś ekspertów znających na wyrywki każdy z jego niezliczonych kruczków znaleźć jeszcze trudniej.
    Który spośród języków naturalnych posiada podobne cechy? Ano angielski, oczywiście. Mówi się nawet, że większość świata mówi jego uproszczoną wersją, czyli “McEnglish”; możliwe, że na podobnej zasadzie istnieje “McC++” ;-] (Prawdę mówiąc, patrząc na niektóre posty na forum Warsztatu, jestem tego pewien ;P).
  • Java to z kolei kolejny bardzo popularny język programowania, który jest wykorzystywany do wielu różnych celów. W każdym zastosowaniu wygląda jednak nieco inaczej: wiadomo na przykład, że inaczej tworzy się aplikacje na J2ME (telefony komórkowe, itp.), a inaczej na J2EE (zastosowania serwerowe). Java ma więc sporo lokalnych ‘dialektów’, a w połączeniu z tym, że jest stosunkowo łatwa do nauki i codziennego posługiwania się, daje się porównać do języka hiszpańskiego.
  • Pascal również do trudnych języków nie należy. Charakteryzuje się ponadto dość rozwlekłą i sztywną składnią, która wymaga używania całkiem sporej ilości słów kluczowych. Słowa te zresztą też do krótkich nie należą, podobnie jak nazwy wbudowanych typów i funkcji.
    Z bardziej znanych języków naturalnych podobnie długie wyrazy i długie zdania oraz w miarę ścisła składnia występują chociażby w niemieckim.
  • W przypadku PHP mamy do czynienia z językiem, który niełatwo przyjmuje “nowinki techniczne” z zewnątrz. Obiektowość na przykład zaczęła być na poważnie wspierana dopiero w piątej jego wersji. Jednocześnie mamy tutaj kilka oryginalnych rozwiązań, które trudno uświadczyć gdzie indziej, jak choćby wstawianie HTML w środku bloku kodu PHP, konstrukcje typu DoSomething() || die(); albo funkcja list. Aha, i wypada też wspomnieć o najpopularniejszym znaku w tym języku, czyli $ ;)
    Podobną awersję do zapożyczeń oraz niespotykane gdzie indziej rozwiązania (np. liczebniki oparte na dwudziestkach, a nie dziesiątkach) w połączeniu z intensywnym użyciem znaczku apostrofa charakteryzują rzecz jasna język francuski.
  • Natomiast Basic miał w założeniu być językiem przede wszystkim dla początkujących. Jego założeniem była łatwość nauki i używania, co miało zapewnić mu popularność. W praktyce wyszło nieco gorzej i jego warianty nie mają w sumie zbyt wielu zwolenników, a cała ta historia przypomina trochę dzieje języka esperanto. Różnica jest taka, że Basic na pewno Nobla nie dostanie :)
  • I wreszcie mamy też C. Kiedyś było to narzędzie bardzo popularne, ale wraz z rozwojem nowszych języków pole jego zastosowań zawęziło się do specyficznych nisz. Z niego jednak wywodzi się cała gałąź języków, które przypominają go co najmniej składniowo: nawiasy klamrowe, sposób deklaracji zmiennych, itd. Rola przodka, który nie jest już szeroko stosowany, w połączeniu ze sporym stopniem skomplikowania sprawia, że C możemy przyrównywać do łaciny.

Trochę to naciągane? :) Zapewne. Między językami programowania a naturalnymi istnieje przecież jedna zasadnicza różnica: tych pierwszych można się nauczyć zdecydowanie łatwiej i szybciej niż tych drugich. Ale w obu przypadkach sprawdza się stwierdzenie, że ‘znać’ to nie znaczy ‘umieć zastosować’. Więc może szukanie analogii podobnych do powyższych nie jest takie zupełnie bez sensu :]

Tags:
Author: Xion, posted under Culture, Thoughts » 8 comments
 


© 2019 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.