W ramach przedmiotu "Programowanie Współbieżne i Rozproszone" zapoznaję się ostatnio z językiem Ada (dokładniej: Ada'95). Choć z początku mnie odpychała (głównie ze względu na nazwę i jej pochodzenie), to jednak po bliższym przyjrzeniu się jej dostrzegłem kilka co najmniej interesujących cech, o których warto wspomnieć.
Operatory and i or
Operatory logiczne and i or (odpowiedniki && i II z C++) NIE wchodzą w tzw. krótkie spięcie - to znaczy, że w przypadku wyrażeń logicznych postaci A or B czy A and B oba składniki obliczane są niezależnie od tego, jaka jest ich wynikowa wartość logiczna. Dla kontrastu przypomnę typowe zastosowanie 'krótkiego spięcia' z języka C:
if(napis && strlen(napis) != 0)
Ada posiada jednak operatory, które zachowują się jak odpowiedniki z C++ - są to and then i or else. Początkujący programiści Ady powinni zwrócić na ten fakt szczególną uwagę.
max(), min() i numeric_limits<>
W większości języków funkcje max() i min(), zwracające największy / najmniejszy element z podanych, zwykle umieszczane są w jakichś bibliotekach - w Adzie zostały osadzone bezpośrednio w typach. Dzięki temu piszemy np. Integer'Max(123, 256)
. Idea ta sięga głębiej - cechy liczb czyli coś, co w C wpisywaliśmy "z palca" a w C++ załatwialiśmy z pomocą std::numeric_limits również jest osadzone w typach. Przykłady dostępnych wartości:
Integer'First -- najmniejsza liczba zapisywalna w tym typie
Integer'Last -- największa liczba zapisywalna w tym typie
Short_Float'Size -- rozmiar typu Short_Float w bitach
Float'Model_Epsilon -- różnica między 1.0 a następną reprezentowalną liczbą zmiennoprzecinkową
Float'SafeFirst -- dolne ograniczenie typu zmiennoprzecinkowego
Float'SafeLast -- górne ograniczenie typu zmiennoprzecinkowego
Float'Digits -- liczba cyfr znaczących w reprezentacji zmiennoprzecinkowej
(przepraszam za byle jakie kolorowanie składni - czas poszukać lepszego pluginu)
Lepszy plugin znaleziony ;)
Ogólna koncepcja atrybutów typów jest w tym języku dość rozbudowana - z ciekawszych zastosowań:
M : array (Natural range 1..16) of Float;
--....
for i in M'range loop -- atrybut range zwraca przedzial indeksow tablicy
-- kod petli
end loop;
M'Length --podaje ilosc elementow tablicy (dlugosc przedzialu)
Put(Integer'Image(214)); --atrybut Image konwertuje zmienna danego typu skalarnego (Integer, Float, etc.) na tekst.
Instrukcja null
Interesującą koncepcją jest instrukcja pusta - null. Ada zmusza nas do użycia jej wszędzie tam, gdzie chcielibyśmy po prostu nie robić niczego - np. wewnątrz pustych funkcji.
Switch
Ada wymusza, by instrukcja case (znana w C++ jako switch) obsługiwała wszystkie dopuszczalne możliwości - w przypadku, gdy nie chcemy obsłużyć pewnych wartości, musimy odwołać się do do wariantu others (znanego w C++ jako default). Konstrukcja case'a jest też dużo wygodniejsza niż jej odpowiedniki w najbliższej rodzinie C++'a - niech zilustruje to przykład:
Get (Znak);
case Znak is
when '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' =>
Put('To jest cyfra dziesietna = ');
Put(Znak);
New_Line;
when 'A' .. 'Z' =>
Put('To jest wielka litera = ');
Put(Znak);
New_Line;
when 'a' .. 'z' =>
Put('To jest mala litera = ');
Put(Znak);
New_Line;
when '_' =>
Put('To jest podkreslnik = ');
Put(Znak);
New_Line;
when others =>
Put('To jest inny znak ');
end case;
Warto zwrócić uwagę na sposób łączenia wariantów za pomocą znaku | oraz określania wariantów będących przedziałami.
Key parameters
W różnych miejscach języka występują pod różnymi nazwami - chodzi oczywiście o powiązania pomiędzy nazwami zmiennych a wartościami. Dzięki temu możemy np. zwięźle inicjalizować obiekty:
type Data is
record
Rok : Positive range 1 .. 2500;
Miesiac : Positive range 1 .. 12;
Dzien : Positive range 1 .. 31;
end record;
--...
--tworzenie obiektu Data:
Dzien := (Miesiac => 6, Dzien => 22, Rok => 2000);
Lub czytelniej wywoływać procedury:
procedure DotProduct (A, B : in Vector; D: out Float) is -- naglowek funkcji
--...
DotProduct(D => Iloczyn, B => Wektor1, A => Wektor2);
W obu tych przypadkach kolejność przekazywania zmiennych do wyrażenia jest inna niż kolejność, w której zostały one określone w strukturze / nagłówku procedury. W tym drugim przypadku daje to możliwość wygodnego przekazywania tylko części parametrów opcjonalnych. Jest to moim zdaniem jedna z lepszych cech tego języka.
Zagnieżdżone funkcje
Ada pozwala zagnieżdżać funkcje i procedury wewnątrz innych funkcji i procedur. Tak naprawdę Ada rozdziela część deklaracyjną od samego ciała danej funkcji, dzięki czemu można tworzyć np. procedury lokalne dla innych procedur, typy danych lokalne dla procedur, etc. Powiem jednak szczerze, że nie miałem jeszcze okazji wykorzystywać w jakiś szczególny sposób tej funkcjonalności w żadnym z języków, w których się ona pojawiła.
Funkcje a procedury
Z punktu widzenia Pascala - różnica słowa kluczowego. Z punktu widzenia C++ - kwestia nazewnictwa (procedurą nazywamy funkcję, która nie zwraca wartości). W Adzie jest inaczej - funkcja nie może wyprowadzać wartości na zewnątrz przy użyciu argumentów out i inout; ponadto musi zwracać wartość i wolno jej występować w kodzie tylko jako element wyrażenia. Przykład:
A := Sin(X); -- legalne, funkcja wystepuje w wyrażeniu (w tym wypadku w przypisaniu)
Sin(X); --nielegalne, funkcja nie jest skladnikiem wyrazenia
Innymi słowy, wartość zwracana nie może pójść donikąd.
Operator /=
Operator /= (odpowiednik != z C++) jest generowany automatycznie, gdy przeciążony operator = (odpowiednik == z C++) zwraca wartość boolean.
Czym jest nowa linia?
Wygląda na to, że wczytując z pliku tekstowego Ada automatycznie zamienia sekwencje znaków nowej linii wg Unixa (ASCII 10) i Windowsa (ASCII 13 + ASCII 10) na znaki ASCII 30. Cytując ze skryptu "Wstęp do programowania w języku Ada'95":
znaki ograniczające linie tekstu w systemie Unix (Character'Val (10)) i w systemie DOS, Windows (Character'Val (13)& Character'Val (10)) są interpretowane w taki sposób, że są zawsze zamieniane na znak (Character'Val (30))
Jest to o tyle ciekawe, że ASCII 30 to znak sterujący RS, czyli Record Separator.
Na dzień dzisiejszy Ada ma jak dla mnie tylko jedną poważną wadę - absolutny brak sensownej dokumentacji w sieci. Pomimo Pascalowej składni i braku darmowych wygodnych narzędzi pod Windowsa jest językiem bardzo interesującym i szczerze mówiąc nie zdziwiłbym się, gdyby dalej była używana w miejscach takich jak medycyna czy wojsko, w których niezawodność oprogramowania czasu rzeczywistego jest kluczowa. A - cytując Fusa - jest przytłaczający dowód na to, że sprzęt wojskowy jest niezawodny: wciąż żyjemy.