Ada

Mureakuha

Loikkaa: valikkoon, hakuun

Ada-kieli kehitettiin alkujaan 1970-luvulla. Nykyisin yleisesti käytössä on kielen uudistettu versio Ada 95. Ada 95 oli ensimmäinen ISO-standardoitu oliokieli. Tällä hetkellä uusin Ada-standardi on ISO/IEC 8652:1995(E).

Ada suunniteltiin suurta luotettavuuttaa vaativien järjestelmien ohjelmointiin. Monista muista kielista poiketen Ada tarjoaa myös erinomaisen tuen laitteistoläheiseen ohjelmointiin, sekä siirrettävyyteen. Ada-ohjelmat ovat käännettävissä usein ilman muutoksia monilla eri alustoilla sekä kääntäjillä.

Toinen Adan keskeisistä suunnitteluperusteista oli ohjelmiston ylläpidettävyys. Ylläpidettävyyden helpottamiseksi Ada suunniteltiin helposti luettavaksi. Tästä johtuen Ada saattaa aluksi vaikuttaa vastaavaa C- tai C++-koodia jaarittelevammalta. Kun otetaan huomioon se, että koodia kirjoitetaan yleensä vain kerran, mutta luetaan useita kertoja, on tämä usein vain hyvä asia, etenkin kun useiden Ada-projektien elinikä mitataan kymmenissä vuosissa.

GCC sisältää nykyisin Ada-kääntäjän, joten Adaa pääsee kokeilemaan varsin helposti.

Sisällysluettelo

Hello World!

with Ada.Text_IO;
procedure Hello_World is
begin
   Ada.Text_IO.Put_Line("Hello World!");
end Hello_World;


Vahva tyypitys

Eräs tärkeimmistä Adan vahvuuksista on tuki vahvalle tyypitykselle. Vahva tyypitys ehkäisee suuren osan ohjelmoijien virheistä jo käännösvaiheessa, koska usein ongelmat johtuvat juuri tyyppiongelmista.

Adassa voidaan määrittää uusia tietotyyppejä, esim.

type Weight_T is range 1 .. 200;
type Height_T is range 1 .. 200;
Weight : Weight_T := 70;
Height : Height_T := 185;

Mikäli nyt yritetään suorittaa seuraava sijoitus

Height := Weight;

antaa kääntäjä ilmoituksen tyyppivirheestä, eikä ohjelma käänny.

Kääntäjä mahdollistaa myös ajonaikaisen lukujen ylivuodon havaitsemisen. Mikäli ylivuoto tapahtuu, ohjelma generoi Constraint_Error keskeytyksen.

Adan kanssa käytetään harvoin valmiita tyyppejä kuten Float tai Integer johtuen tästä aiheutuvasta tyyppittömyydestä. Tämä on usein aiemmin C-, C++- tai Java-kielellä ohjelmoineelle aluksi vaikea ymmärtää.

Numeeristen perustyyppien lisäksi Adassa on mahdollista määrittää ns. merkittyjä (tagged) tyyppejä, joiden kohdalla voidaan käyttää dynaamista sidontaa. Muita mahdollisia tyyppejä ovat tehtävät (task), suojatut (protected) sekä luetelmatyypit. Ada tukee myös osoitintyyppejä (access types).

Taulukkotyypit

Adassa taulukkotyypit ovat kielen perustyyppejä. Taulukoille voidaan vapaasti määrittää ala- ja ylärajat. Alla muutama esimerkki:

type Weekdays is (Mon, Tue, Wed, Thu, Fri);        -- Taulukon indeksityyppi
type Work_Hours_T is array (Weekdays) of Natural;  -- Taulukkotyyppi

Viikon työtunnit saa nyt helposti selvitettyä vaikka seuraavalla tavalla:

declare
   Hours : Work_Hours_T := Get_Work_Hours;
   Total_Hours : Natural := 0;
begin
   for Day in Hours'range loop
      Total_Hours := Total_Hours + Hours(Day);
   end loop;
end;

Ada tukee myös moniulotteisia taulukoita:

declare
   type Matrix_T is array (Integer range <>, Integer range <>) of Integer;
   Matrix : Matrix_T (-3 .. 3, -3 .. 3);
begin
   for X in Matrix'range(1) loop
      for Y in Matrix'range(2) loop
         Matrix(X, Y) := X * Y;
      end loop;
   end loop;
end;

Edeltävässä esimerkissä käytetään tyypin Matrix_T määrittelyyn ns. diskriminoituja parametreja, jotka voidaan määrittää erikseen eri muuttujille (tässä määritettiin 7x7 matriisi, joka on symmetrinen origon suhteen). Yllä esitetyllä tavalla voidaan ohjelmassa välittää matriiseja eri pakkausten ja aliohjelmien välillä ilman huolta muistivuodoista.

Funktiot ja aliohjelmat

Adassa funktiot ja aliohjelmat määritetään eri tavoin. Tämän lisäksi funktiolla ei voida muuttaa suoraan sille annettuja parametreja (poikkeuksena tilanne jolloin funktion parametrina välitetään osoitintyyppi). Alla oleassa esimerkissä esitellään funktio ja kolme aliohjelmaa:

function Area (R : Float) return Float;
procedure Read_Sensor (Acceleration : out Acceleration_T;
                       Sensor_OK    : out Boolean);
procedure Read_Sensor (Velocity  : out Velocity_T;
                       Sensor_OK : out Boolean);
procedure Encode_Data (Frame    :    out Bus.Frame;
                       Velocity : in     Velocity_T;
		       Heading  : in     Heading_T;
		       Lock	: in     Boolean;
		       Error	: in     Error_Code_T);

Aliohjelma Read_Sensor on ylikuorimitettu ensimmäisen parametrin suhteen. Aliohjelmaa Encode_Data kutsuttaessa on suositeltavaa käyttää nimettyä parametrisijoitusta, jolloin aliohjelman kutsu näyttäisi seuraavanlaiselta:

Encode_Data(Frame    => Current_Frame,
            Velocity => Current_Velocity,
            Heading  => Current_Heading,
            Error    => Current_Error,
            Lock     => true);

Esimerkissä formaalit parametrit Error ja Lock on annettu eri järjestyksessä kuin aliohjelman esittelyssä ja tämä on sallittu nimettyä parametrisijoitusta käytettäessä.


Keskeytysten käsittely

Ada-kielessä on tuki keskeytyksille. Poiketen C++:n tai Javan keskeytyksistä Adan keskeytykset ovat perustyyppejä eivätkä luokkia. Keskeytykset voidaan ohjelmassa käsitellä joko blokkitasolla tai korkeammalla tasolla.

Alla olevassa esimerkissä esitellään keskeytys Out_Of_Fuel sekä aliohjelma Steer joka generoi kyseisen keskeytyksen mikäli polttoainetta ei ole jäljellä:

Out_Of_Fuel : exception;

procedure Steer(Direction : in Direction_T) is
   Fuel_OK : Boolean := Fuel_Available(Tank_1);
begin
   if not Fuel_OK then
      raise Out_Of_Fuel;
   end if;
   Fire_Thruster(Direction);
end Fire_Rocket;

...
procedure Turn_Left is
begin
   Steer(Left);
exception
   when Out_Of_Fuel =>
      Self_Destruct;
   when others =>
      raise;
end Turn_Left;

Aliohjelma Turn_Left kutsuu aliohjelmaa Steer ja prosessoi mahdolliset keskeytykset. Mikäli polttoaine on loppu, suoritetaan aliohjelma Self_Destruct. Muut keskeytykset ohjataan ylemmille ohjelmatasoille.


Pakkaukset

Eräs Adan keskeisistä suunnitteluperusteista oli ohjelmistojen ylläpidettävyyden ja uudelleenkäytettävyyden helpottaminen. Tästä johtuen Ada-kielessä pakkauksilla (package) on suuri merkitys.

Pakkausten osat

Jokaisella pakkauksella voi olla kolme osaa: julkinen osa, yksityinen osa sekä pakkauksen runko.

Pakkauksen julkinen osa määrittää pakkauksen ulkoiset rajapinnat. Alla esimerkki, jossa määritetään yksinkertainen pino:

package Stacks is
   type Stack_Type is limited private;

   Stack_Full : exception;
   Stack_Empty : exception;

   procedure Push (Stack : in out Stack_Type; Data : in Integer);
   procedure Pop (Stack : in out Stack_Type; Data : out Integer);
   ...
private
   -- yksityinen osa
   type Stack_Type is record
      ...
   end record;
end Stacks;

Esimerkissä pakkaus Stacks määrittää tyypin Stack_Type, joka on yksityinen ja rajoitettu tyyppi. Rajoitettu (limited) ei salli tyypin objektien kopiointia, eikä kääntäjä määritä rajoitetuille tyypeille vertailuoperaattoreita = tai /=. Lisäksi on määritetty kaksi poikkeusta, joilla ilmaistaan pinon olevan tyhjä tai täysi sekä luonnollisesti aliohjelmat pinon käyttöön.

Koska Stack_Type on määritetty yksityiseksi, ei Stacks-pakkausta käyttävillä muilla pakkauksilla ole keinoa selvittää tyypin rakennetta.

Pakkauksen yksityisessä osassa määritetään julkisessa osassa yksityisinä esitellyt tyypit:

package Stacks is 
   -- julkinen osa
   ...
private
   Stack_Size : constant := 10;
   type Data_Array_Type is array (1 .. Stack_Size) of Integer;

   type Stack_Type is record
      Data : Data_Array_Type;
      Head : Natural := 0;
   end record;
end Stacks;

Yksityisessä osassa nähdään, että tyyppi Stack_Type on määritetty koostuvan taulukosta johon mahtuu 10 elementtiä, sekä taulukkoon osoittavasta muuttujasta (tyyppi Natural on tyypin Integer alatyyppi, joka sisältää numerot 0 .. Integer'last).


Pakkausen runko-osassa määritetään pakkauksen toiminta. Alla yksi mahdollinen Stacks -toteutus:

package body Stacks is
   procedure Push (Stack : in out Stack_Type;
                   Data  : in     Integer) is
   begin
      if Stack.Head = Stack_Size then
         raise Stack_Full;
      end if;

      Stack.Head := Stack.Head + 1;
      Stack.Data(Stack.Head) := Data;
   end Push;

   procedure Pop (Stack : in out Stack_Type;
                  Data  :    out Integer) is
   begin
      if Stack.Head = 0 then
         raise Stack_Empty;
      end if;

      Data := Stack.Data(Stack.Head);
      Stack.Head := Stack.Head - 1;
   end Pop;
end Stacks;

Adan tapauksessa on erittäin suositeltavaa tallettaa pakkauksen julkinen osa ja pakkauksen runko eri tiedostoihin. Osa kääntäjistä jopa vaatii tätä. Lisäksi monet kääntäjät vaativat pakkauksen sisältävän tiedoston nimen olevan sama kuin pakkauksen nimen. Perinteisesti julkinen osa nimetään päätteellä .ads ja pakkauksen runko päätteellä .adb.

Pakkausten käyttö

Adassa kaikki käytettävät pakkaukset tulee määrittää käännösyksikön alussa with -lauseella. Esimerkkinä yllä määriteltyä Stacks pakkausta käyttävän aliohjelman määrittely:

with Stacks;
procedure Stacktest is
   The_Stack : Stacks.Stack_Type;
   A, B : Integer;
begin
   Stacks.Push(The_Stack, 2);
   Stacks.Push(The_Stack, 4);
   Stacks.Pop(The_Stack, A);
   -- A = 4
   Stacks.Pop(The_Stack, B);
   -- B = 2
end Stacktest;


Rinnakkainen suoritus

Ada-kielessä on tuki rinnaikkaisuutta hyödyntävien ohjelmien tekemiseksi. Eräs kielen perustyypeistä on tehtävä (task), jotka määrittelyn jälkeen suoritetaan automaattisesti rinnakkain.

Tehtävien välinen tiedonsiirto

Rinnakkain toimivien tehtävien välillä voidaan vaihtaa tietoa jaetuin muuttujin, suojattujen (protected) olioiden tai kohtaamisten (rendezvous) avulla. Jaetut muuttujat toimivat kuten muissakin kielissä, ja kärsivät kilpailutilanteista.

Suojatut tyypit

Suojattujen tyyppien avulla tehtävien välillä voidaan siirtää tietoa tai tehtäviä voidaan synkronoida keskenään. Suojatuille tyypeille voidaan määrittää funktioita, aliohjelmia sekä vartioituja aliohjelmia (entry). Vartioidulle aliohjelmalle voidaan määrittää suoritusehto, jonka tulee toteutua jotta kyseinen vartioitu aliohjelma voidaan suorittaa. Mikäli ehto ei täyty, jää vartioitua aliohjelmaa kutsunut tehtävä kutsujonoon, jonka kääntäjä määrittää automaattisesti suojatulle tyypille.

Alla suojatun tyypin esittely, esimerkkinä käytetty rengaspuskuria:

   protected type Ring_Buffer_Type is
      function Capacity return Natural;
      entry Get (Item : out Integer);
      entry Put (Item : in Integer);
   private 
      Available_Items : Natural := 0;
      -- muut vaadittavat muuttujat
   end Ring_Buffer_Type;

Rengaspuskurin määrittävä suojattu tyyppi sisältää kaksi vartioitua aliohjelmaa, Put ja Get. Tämän lisäksi suojatulle tyypille on määritetty funktio Capacity, joka palauttaa puskurissa olevien vapaiden elementtien lukumäärän.

Rengaspuskurin toteutus voisi näyttää seuraavalta:

   protected body Ring_Buffer_Type is
      function Capacity return Natural is
      begin
         -- Capacity-funktion toteutus
      end Capacity;
      
      entry Get (Item : out Integer) when Available_Items > 0 is
      begin
	 -- Get-aliohjelman toteutus
      end Get;

      entry Put (Item : in Integer) when Available_Items < Buffer_Size is
      begin
         -- Put-aliohjelman toteutus
      end Put;
   end Ring_Buffer_Type;

Vartioidulle Get-aliohjelmalle on määritetty suoritusehto takaa sen, ettei kyseistä aliohjelmaa suoriteta mikäli puskurissa ei ole tietoa. Put-aliohjelman ehto taas varmistaa sen, ettei puskurille pääse tapahtumaan ylivuotoa. Mikäli suoritusehto ei täyty, vartioitua aliohjelmaa kutsuneen tehtävän suoritus pysäytetään. Tehtävän suoritus jatkuu vasta kun suoritusehto voidaan täyttää.

Koska rengaspuskuri on suojattu tyyppi, voidaan kyseistä puskuria käyttää rinnakkain suoritettavista tehtävistä ilman kilpailutilanteiden vaaraa.

Linkkejä

Tämän dokumentin kopiointi, levittäminen sekä muokkaaminen on sallittua GNU Free Documentation Licensen version 1.2 tai uudemman Free Software Foundationin julkaiseman version mukaisesti, ilman muuttumattomuuslauseketta tai kansitekstejä. Tätä koskee vastuuvapaus.
Kopio lisenssistä (englanniksi) löytyy täältä.
Henkilökohtaiset työkalut