Kdo je Ada?
Ada je programovací jazyk, který vznikl na začátku 80. let z potřeby amerického ministerstva obrany (DoD) vytvořit jeden univerzální jazyk, který by byl použitelný jak pro vložená (embedded) zařízení, tak pro velké systémy s milióny řádky kódu. Byl pojmenován po jisté Augustě Adě Byron, která spolupracovala s Charlesem Babbagem a pravděpodobně byla prvním programátorem na světě. Ada má „pascaloidní“ syntaxi a rozhodně se nejedná o žádnou divokou exotiku, ale o vcelku standardní nástroj zaměřený na tvorbu spolehlivých a přenositelných aplikací. Současná verze Ady se nazývá Ada95 a obsahuje mimo jiné prostředky pro OOP (ovšem jedná se o velice konzervativní „vojenský“ návrh, funkčně tak na úrovni C++ s jednoduchou dědičností, bezpečnostně samozřejmě mnohem výš :-), luxusní jazykové konstrukce pro vícevláknové programování a generické konstrukce.
Ada je poměrně komplexní a málo rozšířený jazyk. Ona malá rozšířenost je pravděpodobně způsobena její „nepříjemností“ pro začátečníky, protože definuje mnohá omezení, ve kterých se programátor musí dobře vyznat, aby si o ně „nenamlátil“. Tato omezení obvykle ocení až zkušenější programátoři pracující na velkých projektech. Nejčastěji se používá pro tvorbu mission/safety critical aplikací. Několik příkladů: řídící software k raketoplánům, jaderným elektrárnám, vlakům TGV, stíhačce Gripen atd. Já osobně jsem v Adě implementoval FTP server, což je dle mého soudu aplikace pro Adu jako stvořená.
Asi nejznámější překladač Ady je GNAT. Je napsán v Adě :-), dostupný pod GPL a pro všechny myslitelné i nemyslitelné platformy.
Tento článek si rozhodně nedělá ambice naučit někoho programovat v Adě. Ada se dá nejlépe naučit přečtením nějaké vhodné tlusté knihy. Ale aby každý viděl, že Ada je úplně obyčejný jazyk, předvedu jedno malé Ahoj světe.
with Ada.Text_Io;
use Ada.Text_Io;
procedure Hello_Main is
begin
Put_Line("Hello world!");
end
Silný typový systém
Řada programátorů si myslí, že to, co nabízí jazyk C, je typová kontrola. V porovnání s různými skriptovacími jazyky sice jistou kontrolu typů při překladu nabízí, ovšem automatické konverze dávají typové bezpečnosti pořádně na frak. Dokonce ani taková Java nedokáže detekovat přetečení celého čísla, Object Pascal z Delphi umožňuje detekovat přetečení a dokonce má intervalové typy, ale to je jen polovičaté řešení.
Právě důmyslný silný typový systém a zakázání automatických konverzí je cestou k rychlejšímu, levnějšímu vývoji bezpečnějších a spolehlivějších aplikací. Typový systém Ady je založen na tom, že jednotlivé proměnné různých typů nelze mezi sebou implicitně konvertovat, ale lze tak činit explicitně. Navíc zavádí tzv. subtypy.
Například standardní 32bitový celočíselný typ by mohl být definován nějak takto:
type Integer is range -2**31 .. 2**31-1;
Tedy nic překvapivého. Překladač pro daný rozsah určí nejvhodnější reprezentaci a kontroluje přetečení. Zajímavějším se to stane, pokud začneme vytvářet specializovanější datové typy pro nějaký konkrétní účel. Například:
type Jablka is new Integer;
type Hrusky is new Integer;
Jedná se o obdobu céčkovského typedef. Ovšem tyto konstrukce nezavádí nové pojmenování existujícího typu, nýbrž typy nové. Proměnné typů Integer, Jablka a Hrusky jsou spolu nekompatibilní. Následující konstrukce způsobí chybu při překladu.
declare
Pocet_Hrusek : Hrusky := 10;
Pocet_Jablek : Jablka;
begin
pocet_jablek := pocet_hrusek; --nelze přiřazovat hrušky a jablka! (přestože mají stejnou fyzickou reprezentaci)
end;
Takže nám jazyk zaručí, že nikde v programu nezaměníme hrušky a jablka. Pokud bychom to přece jenom potřebovali, můžeme si to explicitně poručit. Následující příklad však není programátorská chybka z nepozornosti, ale buď čirý úmysl, nebo čirá sabotáž.
Pocet_Jablek := Jablka(Pocet_Hrusek);
Integritní omezení
Integritní omezení pomocí intervalů celočíselných typů jsou velice užitečná pro eliminaci logických chyb v programu. Pokud například dopředu víme, že v programu nikdy nebudeme mít víc jak 100 jablek a víc jak 50 hrušek a budeme chtít vyloučit záporné počty plodů (malvic :-), uděláme to takto.
type Jablka is new Integer range 0..100;
type Hrusky is new Integer range 0..50;
Následující řádek způsobí chybu při překladu:
Pocet_Jablek := 101;
Při konverzi typů se kontrolují hodnoty proměnných, a pokud by mělo dojít k porušení integritních pravidel, vygeneruje se výjimka. Následující kousek kódu tedy projde, jenom pokud je Pocet_Jablek menší než padesát.
Pocet_Hrusek := Hrusky(Pocet_Jablek); --může vyvolat výjimku typu Constraint_Error (chyba mezí)
Příklad s ovocem byl sice názorný, ale poněkud nepraktický. Dovolím si proto ještě jednu ukázku, která demonstruje standardní Adou definované celočíselné typy. Tentokrát zapojíme adovskou specialitu, a tou jsou podtypy (subtypy).
type Integer is range -2**31 .. 2**31-1;
subtype Natural is Integer range 0 .. 2**31-1; --podtyp přirozené číslo (s nulou)
subtype Positive is Integer range 1 .. 2**31-1; -- podtyp kladné číslo
Subtypy jsou užitečné tam, kde chceme omezit rozsah nějakého typu, ale nechceme zavádět typ nový. Všechny proměnné určitého typu a jeho subtypů mohou být mezi sebou libovolně přiřazovány bez jakýchkoli explicitních konverzí (přetypování). Generovaný kód samozřejmě stále kontroluje hodnoty a v případě překročení mezí vygeneruje výjimku.
Pokud nyní budu programovat například spojový seznam, nikdy nenadeklaruji proměnnou Pocet_Atomu (označující počet prvků v seznamu) jako typ Integer, nýbrž jako Natural. Přidám tím do programu další informaci a omezím možnost vzniku chyby. Nikdy neprojde odebrání atomu z prázdného seznamu, nikdy nebude počet atomů menší než nula. Toto je opravdový příklad ze života, kdy mi překladač odhalil chybu, která by v C mohla skončit nějakým veselým Access Violation nebo Segmentation Fault. A já jsem tehdy mohl jít včas spát :-).
Ještě poznamenám, že Ada umožňuje definovat intervaly i u floating-point typů. Užitečné to může být třeba pro úhly v radiánech:
type Uhel is new Float range 0.0 .. 2.0 * pi;
Další kouzla
Představte si, že máme nějakou aplikaci (třeba software k vesmírné sondě :-), která počítá plochy nějakých objektů. Chceme definovat integritní omezení v duchu fyzikálních zákonů.
type Metry is new Float;
type Ctverecni_Metry is new Float;
Když teď budeme chtít spočítat nějakou plochu, budeme muset explicitně přetypovávat. Což je poněkud neelegantní.
declare
vyska : Metry := 10.0;
sirka : Metry := 15.0;
plocha : Ctverecni_Metry;
begin
plocha := Ctverecni_Metry(vyska*sirka);--musíme přetypovávat
end;
Ale my víme, že když násobíme metry, výsledek je vždy ve čtverečních metrech. Nešlo by takové pravidlo šikovně zadrátovat do jazyka? Šlo! Vyřešíme to přetížením operátoru násobení pro datový typ metry tak, aby vracel metry čtvereční.
function "*" (Left, Right : Metry) return Ctverecni_Metry is
begin
return Ctverecni_Metry(Float(Left)*Float(Right));
-- před násobením jsme přetypovali na float, abychom zabránili rekurzi
-- takto donutíme překladač použít standardní násobení pro typ Float
end;
Nejen, že teď při výpočtu plochy nemusíme přetypovávat, ale následující (fyzikálně nekorektní) konstrukce dokonce způsobí chybu při překladu. A to je přesně to, co jsme chtěli.
declare
vyska : Metry := 10.0;
sirka : Metry := 15.0;
plocha : Metry;
begin
plocha := vyska*sirka; --zde nastane chyba prekladu
end;
Co to stojí?
Řada programátorů jistě namítne, že po použití takovýchto konstrukcí musí být výsledné programy pomalé a velké. Není to pravda. Spousty kontrol se provádějí při překladu zdrojového kódu (protože to je to, co zrychluje vývoj) a run-time kontroly jsou jen tam, kde je to potřeba. Je nasnadě, že když přiřazujete proměnnou typu přirozené číslo do proměnné typu celé číslo, není žádná kontrola potřeba (celá čísla jsou nadmnožina přirozených čísel. Navíc tím, že různými omezeními přidáte do zdrojáku informace, umožníte progresivní high-level optimalizace, které by jinak nebyly možné. Překladač GNAT je postaven na back-endu z GCC, takže disponuje rovněž kvalitními low-level optimalizacemi. Adovské programy snesou rychlostní srovnání s C/C++. Pro kontrolu celočíselných mezí existuje v současných procesorech speciální instrukce, takže režie s tím spojená se u reálných aplikací počítá na jednotky procent. Ostatně dnes jsou v matematických operacích velice rychlé i Java a C#.
Závěrem
Doufám, že po přečtení tohoto článku každý tuší, co je to silný typový systém a jak taková věc může programování zpříjemnit. Při vývoji aplikací, kde jde o lidské životy, je taková podpora ze strany jazyka takřka nutností, při vývoji běžných aplikací to šetří peníze a nervy. Ada nabízí pro šetření nervů ještě mnohem víc. Například atributy typů užitečné pro zamezení indexování pole mimo jeho rozsah, thread-safe objekty, důmyslné skrývání implementace či překladačem určovaný způsob předávání parametrů podprogramům.