# Ada (linguaggio di programmazione)

Ada è un linguaggio di programmazione sviluppato verso la fine degli anni settanta su iniziativa del Dipartimento della Difesa (DOD) degli Stati Uniti. Sia le specifiche che lo sviluppo del linguaggio furono affidati a bandi di gara. Tra le 17 proposte inviate in seguito al bando indetto dal DOD, fu scelto nel 1979 il progetto di Jean Ichbiah, che all'epoca lavorava presso la CII Honeywell Bull. Le specifiche divennero uno standard ANSI e ISO nel 1983, seguito dalle successive revisioni nel 1995, 2005 e 2012. Un sottoinsieme hard real-time del linguaggio è noto come profilo Ravenscar.
Ada combina principi e tecniche provenienti da diversi paradigmi di programmazione, in particolare programmazione modulare, programmazione orientata agli oggetti, programmazione concorrente e calcolo distribuito. Sebbene l'interesse del DOD vertesse principalmente sullo sviluppo di applicazioni militari, Ada è un linguaggio general purpose che si presta all'utilizzo in qualsiasi dominio applicativo. L'origine militare si rivela però nella presenza di caratteristiche fortemente orientate alla sicurezza del codice; per questo motivo, il linguaggio viene ancora oggi usato in molti contesti in cui il corretto funzionamento del software è critico, come astronautica, avionica, controllo del traffico aereo, finanza e dispositivi medici.
I compilatori Ada impiegati per lo sviluppo di software mission-critical devono seguire un processo di certificazione secondo lo standard internazionale ISO/IEC 18009 (Ada: Conformity Assessment of a Language Processor), implementato nella suite Ada Conformity Assessment Test Suite (ACATS), parte integrante del processo di certificazione svolto da laboratori autorizzati dall'Ada Compiler Assessment Authority (ACAA).
Il nome iniziale del linguaggio doveva essere DOD-1, ma venne in seguito cambiato in Ada in onore di Ada Lovelace, illustre matematica dei primi anni del XIX secolo, accreditata come la prima programmatrice della storia per aver sviluppato un algoritmo per il calcolo dei numeri di Bernoulli sulla macchina analitica di Charles Babbage.

## Caratteristiche

Ada eredita alcune caratteristiche stilistiche fondamentali da ALGOL, rispetto al quale aggiunge molte funzionalità basilari (come il sistema di tipi, i record, i puntatori o le enumerazioni, implementati in buona parte in stile Pascal) e funzionalità avanzate proprie dei moderni linguaggi di programmazione (polimorfismo, ereditarietà, eccezioni, tasking).
Il linguaggio fornisce un gran numero di controlli sia statici (a tempo di compilazione) sia dinamici (a runtime), che prevengono un'ampia varietà di errori (uso errato dei parametri, errori di tipo, violazione di range e off-by-one). I controlli dinamici sono disattivabili se si vuole massimizzare l'efficienza, tramite il pragma Suppress, o tramite switch specifici dei vari compilatori. Sicurezza e affidabilità del codice sono infatti uno dei principali aspetti che hanno guidato lo sviluppo del linguaggio.

### "Hello, world!" in Ada

Un Hello world in Ada è il seguente:

## Sintassi

A differenza della maggior parte dei linguaggi di programmazione, Ada è insensibile alle maiuscole. Le convenzioni stilistiche più comuni prevedono di scrivere le keyword del linguaggio interamente in minuscolo (o, meno comunemente, interamente in maiuscolo) e i nomi di tutti gli identificatori (tipi, variabili, subroutine, package etc.) in snake case con l'iniziale di ogni parola maiuscola. I commenti sono su singola linea, iniziati da un doppio trattino -- e terminati dal ritorno a capo. Per gli identificatori e i commenti Ada supporta il set di caratteri ISO 10646, permettendo quindi di scrivere codice in caratteri non ASCII, e la lunghezza massima consentita per gli identificatori è legata all'implementazione (nelle implementazioni standard deve essere pari a non meno di 200 caratteri).
La sintassi di Ada impiega pochi simboli, prediligendo l'uso di parole in lingua inglese. I simboli sono l'assegnazione :=, gli operatori aritmetici elementari (+, -, *, / e **), gli operatori di comparazione (<, <=, >, >=, =, /=), i due punti : per dichiarare il tipo di una variabile, la freccia => negli aggregati, la virgola , come separatore negli aggregati, l'operatore di accesso ai campi ., l'indicazione di range .., il tick ' per accedere agli attributi, il box <> per indicare un parametro variabile nelle dichiarazioni, il punto e virgola ; per separare i parametri di una routine e per terminare le istruzioni. L'istruzione nulla è costituita dalla keyword null terminata da punto e virgola, mentre una riga vuota terminata da punto e virgola non è valida. I restanti operatori sono implementati tramite keyword (and, or, rem, mod etc.).
Ada è un linguaggio strutturato e i blocchi sono delimitati da keyword. La chiusura del blocco contiene un riferimento o identificatore per capire immediatamente a quale apertura appartiene ed evitare confusione, impedendo inoltre potenziali dangling else o analoghi errori. Ad esempio, una subroutine contenente un ciclo for appare come:

### Parole riservate

Il linguaggio, in riferimento allo standard Ada 2012, ha 73 parole riservate:
Esiste un'omonimia tra le parole riservate access, delta, digits, mod e range e gli omonimi attributi, ma essendo gli attributi sempre preceduti da un apostrofo (detto tick) non esiste rischio di ambiguità.

### Dichiarazioni di variabile

Le dichiarazioni di variabile in Ada si effettuano indicando il nome dell'entità dichiarata, seguito da : e poi dal tipo, quest'ultimo preceduto da eventuali specificatori (constant, aliased, not null etc.). Un valore di inizializzazione statico o dinamico può essere indicato facoltativamente dopo il tipo, separato da :=, e la dichiarazione è infine terminata da ;. Le dichiarazioni possono essere collocate nella specifica di un package o nella parte dichiarativa di una routine o di un blocco declare. Più variabili possono essere dichiarate nella stessa dichiarazione, separando i nomi con virgole, ma si tratta solo di zucchero sintattico e tecnicamente le dichiarazioni sono comunque distinte ed indipendenti fra loro, quindi ad esempio un'eventuale espressione di inizializzazione viene valutata una volta per ciascuna variabile.
È possibile definire variabili costanti facendo precedere il tipo dalla keyword constant, nel quale caso è obbligatorio fornire un'inizializzazione, ed è inoltre possibile definire costanti numeriche omettendo il tipo prima dell'inizializzazione, che deve consistere in un'espressione statica, ovvero nota a tempo di compilazione (il tipo della costante viene inferito dall'inizializzatore letterale). La differenza tra variabili costanti e costanti numeriche consiste nel fatto che le prime sono variabili calcolate a runtime, mentre le seconde vengono risolte a tempo di compilazione.

### Goto

A differenza di molti linguaggi moderni, Ada conserva l'istruzione goto. Pur essendo generalmente deprecata nella programmazione strutturata, è infatti un'istruzione utile per la generazione automatica di codice a partire da sorgenti in altri linguaggi o specifiche formalizzate ad alto livello. È inoltre accettabile in alcuni contesti, come il salto verso il termine di un blocco (ad esempio un'iterazione di un ciclo) o l'uscita da cicli profondamente annidati, contesto nel quale è più leggibile rispetto ad analoghe istruzioni break o continue in altri linguaggi in quanto il punto di arrivo del salto nel codice è chiaramente indicato dalla label (mentre il break in linguaggi come il C può essere error prone).

## Sistema di tipi

Ada è un linguaggio fortemente tipizzato e typesafe. A differenza del C e dei suoi derivati, Ada non ammette conversioni di tipo implicite (tranne che per i tipi universali e gli access type anonimi) e la conversione di tipo si effettua scrivendo il nome del tipo seguito dalla variabile da convertire tra parentesi, ad esempio:
Il tipo di un letterale può essere indicato esplicitamente con una sintassi simile alla conversione, aggiungendo un tick prima della parentesi aperta. Questo è utile soprattutto in caso di ambiguità, quando uno stesso letterale (ad esempio una stringa, o un valore enumerativo) può riferirsi a tipi diversi.

### Tipi predefiniti

A differenza del C e dei suoi derivati, in Ada non esistono keyword che identificano tipi primitivi, e tutti i tipi predefiniti sono definiti nella standard library. I tipi predefiniti fondamentali (come Integer, Float o Character) sono definiti nel package Standard, ma per massimizzare la portabilità è considerata buona pratica non usare direttamente i tipi predefiniti e definire invece i propri, anche per i tipi numerici più semplici.
I tipi definiti nel package Standard sono sempre visibili in ogni parte del programma, mentre gli altri tipi definiti nei restanti package della libreria standard richiedono una clausola with per essere visibili. I tipi definiti in Standard sono:
* Integer: tipo discreto intero, che assume valori in un intervallo di estensione legata all'implementazione, non inferiore a $-2^{15}+1$ .. $+2^{15}-1$.
** Natural: sottotipo di Integer che può assumere solo valori non negativi;
** Positive: sottotipo che può assumere solo valori positivi non nulli;
* Float: tipo in virgola mobile con almeno sei cifre;
* Duration: tipo in virgola fissa, usato per esprimere un tempo in secondi;
* Character, Wide_Character, Wide_Wide_Character: tipi speciali di enumerazione, usati per i caratteri di testo con codifica rispettivamente a 8, 16 e 32 bit (gli ultimi due sono stati aggiunti rispettivamente in Ada 95 e in Ada 2005)
* String, Wide_String, Wide_Wide_String: stringhe di lunghezza fissa, costituite da array di caratteri. Altre due varietà di stringhe, più flessibili ma anche più pesanti dal punto di vista computazionale, sono definite in altri package della libreria, e sono Ada.Strings.Bounded.Bounded_String (per stringhe di lunghezza variabile fino ad un valore massimo) e Ada.Strings.Unbounded.Unbounded_String (per stringhe di lunghezza variabile senza restrizioni), i cui rispettivi package forniscono anche routine per la manipolazione;
* Boolean: tipo enumerativo che può assumere i valori False e True, e che gode di una semantica particolare.
I package System e System.Storage_Elements forniscono alcuni tipi che sono utili per la programmazione a basso livello:
* System.Address: rappresenta un indirizzo di memoria;
* System.Storage_Elements.Storage_Offset: rappresenta un offset di memoria, che può essere aggiunto o sottratto ad un indirizzo per ottenere un altro indirizzo;
* System.Storage_Elements.Storage_Count: un sottotipo di Storage_Offset che può assumere solo valori non negativi, usato per rappresentare la dimensione di una struttura dati;
* System.Storage_Elements.Storage_Element: rappresenta l'unità minima di memoria indirizzabile (ovvero un singolo byte nella maggior parte delle implementazioni);
* System.Storage_Elements.Storage_Array: array di Storage_Element.

### Dichiarazione di tipi e sottotipi

I tipi possono essere definiti usando la keyword type, sotto forma di enumerazione, come tipo in virgola mobile con la keyword digits o a partire da un altro tipo preesistente, usando la keyword new, mentre l'attributo 'Base di un tipo fornisce il tipo base da cui deriva. A partire da un tipo è possibile definire un sottotipo (con la keyword subtype), che consiste in un insieme di valori contenuto nel dominio del tipo da cui deriva.

### Tipi enumerativi

I tipi enumerativi possono essere specificati indicando tra parentesi i possibili valori. I letterali di tipo enumerativo possono confliggere, e in tale caso per risolvere l'ambiguità si indica il tipo esplicitamente. È possibile specificare un range di valori enumerativi, ed è possibile dichiarare sottotipi enumerativi che assumano solo una parte dei valori del tipo da cui derivano.
Per i tipi enumerativi sono definiti automaticamente gli operatori di confronto (l'ordine è dato dalla posizione nella dichiarazione, partendo dal valore minimo verso il massimo), e dispongono di diversi attributi utili, tra i quali 'First e 'Last, che restituiscono il primo e l'ultimo valore del tipo, 'Succ e 'Pred, che restituiscono i valori precedente e successivo, 'Pos e 'Val, che restituiscono rispettivamente l'indice posizionale (zero-based) del valore (ovvero la posizione nella dichiarazione) e il valore associato ad un indice.

### Array

Gli array sono definiti tramite la keyword array, specificando tra parentesi la natura degli indici (che possono essere di un qualsiasi tipo discreto). Oltre al tipo è possibile specificare un range per gli indici dell'array, indicando non la dimensione della struttura, ma una coppia di valori minimo e massimo per l'indice, eliminando alla radice il problema della scelta tra array zero-based e one-based. L'accesso agli elementi di un array avviene segnando l'indice tra parentesi tonde.
Un tipo array può essere definito anonimamente nella dichiarazione di variabile, oppure può essere dichiarato un tipo specifico per l'array con la keyword type. La differenza fondamentale è che nel primo caso due array non possono essere assegnati fra loro, anche se dichiarati con un tipo anonimo che sembra lo stesso, in quanto si tratta formalmente di due tipi diversi (anche se la dichiarazione avviene nella stessa riga separando i nomi delle variabili con la virgola, in quanto tale notazione è solo zucchero sintattico per due dichiarazioni distinte).
Gli array supportano lo slicing, assegnando contemporaneamente un range (non necessariamente statico) di indici, anche con sovrapposizione di elementi.
È possibile dichiarare array mutlidimensionali nativi, separando gli indici con virgole, oppure sotto forma di array di array (e quindi jagged array), in questo caso avendo cura di fare attenzione all'ordine delle dichiarazioni.
Per gli array è possibile utilizzare nelle dichiarazioni o nelle istruzioni dei letterali (detti aggregati), costituiti da un insieme di valori tra parentesi tonde, separati da virgole. Gli aggregati possono essere posizionali oppure nominativi: nel primo caso la corrispondenza del valore con l'indice cui si riferisce è data dalla posizione nell'aggregato (l'ordine dei valori deve seguire quindi quello degli elementi dell'array), nel secondo caso invece per ogni valore si specifica l'indice, usando il simbolo => per separarlo dal valore. È possibile assegnare uno stesso valore a più indici, usando il range .. se sono consecutivi o una pipe | per elencare dei valori non consecutivi. La sintassi degli aggregati è rigorosa e non consente inizializzazioni parziali, ma è possibile specificare individualmente solo alcuni elementi e completare l'assegnazione con un valore per tutti i restanti elementi, usando la keyword others.
È possibile applicare agli array unidimensionali di valori Boolean che abbiano stesso tipo e lunghezza gli operatori not, and, or e xor, e il risultato è un array le cui componenti sono calcolate applicando l'operatore alle singole coppie di componenti degli array operandi.

### Record

I record possono essere definiti con la keyword record, specificando i campi analogamente alle dichiarazioni di variabili (inclusa la possibilità di assegnare valori predefiniti). È possibile accedere ai campi di un record con la dot notation, ed utilizzare aggregati costruiti analogamente a quelli degli array. È inoltre possibile definire un record senza campi con la coppia di keyword null record, utile per definire tipi astratti o per estendere un tipo senza aggiungere nuovi campi.

### Tipi parametrici

È possibile utilizzare dei parametri (detti discriminanti) nella dichiarazione di un tipo, che verranno specificati quando si dichiarano le variabili o si deriva un tipo non parametrico.

### Tipi limitati

Un ulteriore meccanismo di controllo sui tipi è dato dalla possibilità di definire tipi limitati, tramite la keyword limited. I tipi limitati dispongono solo delle operazioni dichiarate nel package, quindi non sono assegnabili con := e non dispongono delle operazioni di confronto predefinite = e /=. Un tipo privato può essere definito limitato ma implementato internamente come non limitato, in questo caso il tipo è limitato all'esterno del package ma non limitato nella parte privata dello stesso, garantendo flessibilità all'interno del package, dove le operazioni predefinite rimangono disponibili, e allo stesso tempo il controllo sul tipo rispetto a chi lo usa all'esterno del package.

## Modularità

Ada è un linguaggio strutturato e permette di dividere il codice in più unità di compilazione (routine e package), che possono essere compilate separatamente.

### Routine

Ada supporta due tipi di routine, funzioni e procedure, dichiarate con le keyword function e procedure. Le prime hanno un valore di ritorno e possono essere usate solo nel contesto di un'espressione (in Ada, a differenza dei linguaggi derivati dal C, non esistono expression statement), mentre le seconde non hanno valore di ritorno e possono essere usate solo come istruzione. Sia le procedure sia le funzioni sono costituite da due parti distinte, una parte dichiarativa tra le keyword is e begin, che può contenere solo dichiarazioni (come variabili, tipi o altre routine annidate), e una parte contenente solo istruzioni, tra le keyword begin e end. È possibile inserire dichiarazioni fuori dalla parte dichiarativa usando un blocco declare, e le relative dichiarazioni sono visibili solo al suo interno. Una routine può costituire un'unità di compilazione autonoma oppure può essere dichiarata ed implementata all'interno di un package o nella parte dichiarativa di un'altra routine.
I parametri di una routine vengono specificati tra parentesi tonde, e Ada supporta il passaggio di parametri sia per valore sia per riferimento, con tre diversi modi: in, out e in out. I parametri passati in modo in possono solo essere letti dalla routine, quelli in modo out possono solo essere scritti, quelli in modo in out possono essere letti e scritti. Il modo di un parametro viene specificato dopo i : e prima del tipo, e se omesso viene assunto in come predefinito. È possibile definire un valore predefinito per i parametri, che in questo modo diventano opzionali e possono essere omessi nelle chiamate alla routine: in tale caso viene usato il valore predefinito. Per i parametri e i valori di ritorno di tipo access è possibile specificare la null exclusion, che causa un'eccezione a runtime se il parametro (o il valore restituito) sono null. Le routine vengono invocate facendo seguire al nome delle stesse una coppia di parentesi tonde contenenti i parametri, ma se una routine viene dichiarata o chiamata senza passare alcun parametro le parentesi tonde possono essere omesse. Le routine definite su tipi tagged possono essere richiamate anche con la dot notation rispetto al parametro di tipo tagged.
Anche gli operatori sono funzioni, definite indicando il nome dell'operatore tra doppi apici, e Ada supporta l'overloading degli stessi, così come l'overload e l'override delle routine in generale.
Gli operatori sono identificati dalle seguenti keyword:

### Package

La modularità del codice ad un livello più astratto rispetto alle routine è ottenuta tramite i package. Un package è suddiviso in due parti distinte, specifica e implementazione, e alcuni compilatori (come GNAT) obbligano ad inserire una sola unità di compilazione per file, dividendo inoltre specifica e implementazione in due file separati (rispettivamente .ads e .adb). La specifica rappresenta l'interfaccia del package accessibile dall'esterno e può essere compilata indipendentemente dall'implementazione, facilitando i test nelle fasi iniziali di sviluppo. L'implementazione contiene il codice effettivo delle routine definite nell'interfaccia, più altre eventuali routine non accessibili dall'esterno del package. I package possono essere compilati separatamente e, in caso di modifica all'implementazione di un package senza alterarne la specifica, eventuali altri package da esso dipendenti non necessitano di essere ricompilati.
Le dichiarazioni delle routine contenute devono essere inserite nella specifica del package (file .ads), mentre l'implementazione si colloca nel body (file .adb). Specifica e implementazione devono avere una type conformance completa, ovvero il nome della routine, l'eventuale tipo di ritorno, il numero, tipo, modo, ordine, valore predefinito e nome dei parametri devono essere identici nella specifica e nell'implementazione, a meno di alcune differenze non sostanziali: un letterale numerico può essere sostituito con uno formalmente diverso ma con lo stesso valore, a un identificatore può essere aggiunto un prefisso in dot notation, un'indicazione esplicita del modo in può essere omessa, un insieme di parametri dello stesso sottotipo possono essere indicati separatamente. Tutte le routine dichiarate nella specifica devono essere necessariamente implementate nel body, salvo alcune eccezioni: le procedure nulle (la cui dichiarazione termina con is null, ed è equivalente a una procedura contenente solo un'istruzione nulla), le routine astratte (che non hanno un'implementazione) e le funzioni costituite da un'espressione condizionale (che viene inserita direttamente nella specifica).
Si può creare una gerarchia di package in due modi: con l'annidamento, definendo un package dentro l'altro, e con la parentela, definendo un package come figlio dell'altro (separando padre e figlio con la dot notation). I package figli possono essere inoltre definiti come privati, e in questo modo sono totalmente inaccessibili fuori dal package padre.
Tali tipi avranno perciò una vista parziale nella parte pubblica, e una vista completa nella parte privata del package. I package figli condividono la parte privata del padre ma non dei fratelli, tuttavia è possibile importare un package fratello in modo da poterne vedere anche la parte privata usando la clausola private with invece della solita with.

### Generics

In Ada le routine o i package possono avere dei parametri risolti staticamente a tempo di compilazione, specificati facendo precedere l'unità dalla keyword generic e specificati tra parentesi tonde quando si istanzia l'unità generica. I generics sono un meccanismo statico che consente un riuso del codice, permettendo di istanziare la stessa unità con parametri di tipo differenti, ma a differenza di altri linguaggi i parametri generici possono essere non solo tipi, ma anche valori o routine. Nel caso di package generici, non può essere usata su essi la clausola use (può essere usata solo sulle loro istanze concrete) ed ogni eventuale loro package figlio deve necessariamente essere generico.
Ad esempio, una procedura per scambiare due elementi può essere parametrizzata rispetto al tipo degli stessi
ed essere istanziata per l'uso su tipi diversi:
Analogamente un package può implementare uno stack generico
che può essere istanziato per contenere oggetti di differente tipo e scegliendo di volta in volta la capacità massima:

#### Parametri di tipo

Nel caso più semplice, un parametro di tipo generico è istanziabile e ha solo la definizione di assegnamento e uguaglianza. Possono essere specificati diversi attributi del parametro di tipo, che ne modificano l'interfaccia: può essere limitato, può avere dei discriminanti (anche non specificati, indicati con il box <>, in tal caso il tipo non è istanziabile), può essere tagged, astratto, può essere discendente di un tipo preesistente o può essere un'interfaccia con una o più interfacce progenitrici. Alcuni esempi di parametri di tipo generici:
Per i tipi enumerativi e numerici esistono anche delle indicazioni particolari, e per ognuno di essi sono disponibili le operazioni predefinite per tale tipo.

#### Parametri routine

Poiché per un tipo generico si assume l'esistenza delle sole funzioni predefinite per quella categoria di tipi, può essere necessario passare come parametro anche una o più routine. Ad esempio, implementando un albero binario di ricerca che possa contenere chiavi di un tipo qualsiasi, per effettuare le operazioni di inserimento o ricerca è necessario poter comparare le chiavi, ma l'operatore di confronto < non è predefinito per ogni tipo. Si può quindi ovviare al problema passando tale operatore come parametro generico. I parametri generici per le routine devono essere quindi passati esplicitamente quando si istanzia il package, ma possono essere omessi se nella dichiarazione del parametro generico si specifica is <>. In questo caso la routine viene scelta automaticamente quando si istanzia il package, a patto che sia definita e visibile, sia unica e abbia completa type conformance con la dichiarazione del parametro generico.

## Memoria dinamica

La memoria dinamica (chiamata storage pool) è gestita ad alto livello ed è typesafe. Il linguaggio non fornisce puntatori flessibili come quelli del C (che sono una tra le principali fonti di bug, errori e vulnerabilità), ma utilizza dei riferimenti (detti access type) che conservano informazioni sull'accessibilità degli oggetti ai quali fanno riferimento e seguono precise regole di accessibilità, prevenendo il problema dei dangling pointer. La versatilità del linguaggio per applicazioni a basso livello è comunque garantita, grazie all'attributo 'Address e al package System, che permettono di manipolare indirizzi di memoria raw. L'interfaccia Interfaces.C.Pointers fornisce inoltre dei puntatori in stile C, utili quando si interfaccia un'applicazione Ada con una in C.
La semantica del linguaggio permette la garbage collection, la cui presenza è però legata all'implementazione: solitamente è assente nei compilatori per le architetture native (in quanto può influire in maniera imprevedibile sul timing, e questo è un effetto deleterio nei sistemi real-time) ma è talvolta presente per i compilatori che hanno come architettura target la JVM. La deallocazione può essere effettuata manualmente, istanziando l'unità generica Ada.Unchecked_Deallocation, che deve essere usata con attenzione per evitare di deallocare oggetti nello stack o creare dangling pointer. Per aumentare la sicurezza si può applicare un pattern di smart pointer, creando oggetti che contano e gestiscono autonomamente i riferimenti alle risorse, in modo che il programmatore del client non debba deallocare niente in maniera esplicita.

## Programmazione orientata agli oggetti

A differenza di molti linguaggi orientati agli oggetti, Ada non ha un costrutto per le classi analogo a C++ o Java. Tra i principali aspetti della programmazione OOP vi sono la possibilità di distinguere il tipo di un oggetto a runtime, di definire un tipo a partire da un altro e di permettere a tali tipi di ereditare le operazioni primitive del tipo da cui deriva. In Ada la differenza tra variabili che sono "oggetti" e che non lo sono consiste nel fatto che le prime conservano a runtime le informazioni sul proprio tipo, consentendo il polimorfismo e il dynamic dispatch.

### Ereditarietà

L'ereditarietà si ha tramite estensione, che rende possibile aggiungere nuovi campi, mantenendo anche quelli ereditati. È possibile convertire un oggetto (record tagged) di un sottotipo in un tipo antenato, mentre non è possibile fare il contrario, ma è possibile assegnare un oggetto di tipo antenato ad un tipo discendente usando un aggregato che completi i campi mancanti (extension aggregate). L'estensione S di un tipo tagged T tramite la keyword new eredita anche le operazioni primitive per T, ovvero quelle subroutine dichiarate nello stesso package in cui è dichiarato T e che abbiano un parametro o un risultato di tipo T. I tipi derivati hanno quindi un'interfaccia che è sempre un sovrainsieme di quella del tipo da cui derivano, e l'implementazione effettiva delle operazioni ereditate può essere modificata tramite override (l'indicazione esplicita di override nella dichiarazione dell'operazione tramite la keyword overriding, o viceversa di non override con not overriding, è facoltativa ma costituisce un utile controllo statico a tempo di compilazione). Le funzioni di un tipo tagged possono essere richiamate con la dot notation sulla variabile dell'oggetto. L'incapsulamento dello stato interno degli oggetti non è diverso da quello per i tipi non tagged, ed è ottenuto usando il meccanismo di incapsulamento dei package. L'estensione di un tipo può avvenire anche privatamente, per cui i campi aggiunti nell'estensione non sono visibili all'esterno del package.

### Dynamic dispatch

Con il termine "classe" in Ada si indica un insieme di tipi (class wide), costituito da un tipo tagged e da tutti i tipi da esso derivati direttamente o indirettamente. È possibile definire operazioni che hanno parametri o risultato di un tipo class wide usando l'attributo 'Class, ad esempio per un tipo T il suo tipo class wide viene indicato con T'Class. La differenza tra un'operazione con un parametro di tipo T e uno di tipo T'Class è che nel primo caso la scelta della routine da eseguire è determinata staticamente a tempo di compilazione, nel secondo caso è determinata dinamicamente a runtime (dynamic dispatch). Se nell'esempio precedente si aggiunge al tipo Persona la seguente operazione primitiva
si ha che la funzione restituirà sempre zero per tutti gli oggetti, anche dei tipi come Lavoratore che avessero salario non nullo, perché al suo interno viene sempre richiamata staticamente la funzione Salario definita per il tipo Persona, che restituisce zero. Se invece l'operazione è definita come
il dispatch della funzione Salario avviene dinamicamente e il risultato restituito è quello corretto anche per gli oggetti di tipo Lavoratore.

### Tipi astratti e interfacce

Se un tipo tagged viene dichiarato astratto, tramite la keyword abstract, non è possibile dichiarare variabili di quel tipo, ma solo usarlo come base da cui derivare altri tipi. I tipi astratti possono avere componenti e routine concrete, ma anche routine a loro volta definite come astratte, che non sono provviste di implementazione. Quando si deriva un tipo concreto da un tipo astratto, tutte le sue routine astratte devono necessariamente essere oggetto di override. Un'interfaccia è un tipo dichiarato con la keyword interface, ed è analogo ad un tipo astratto ma ha maggiori restrizioni, in quanto non può avere componenti né routine concrete, salvo procedure nulle o routine con parametri class wide.
Ada ha un meccanismo di ereditarietà singola per le implementazioni e multipla per le interfacce, simile al Java, per cui è possibile definire tipi che estendono al massimo un tipo concreto o astratto, ma allo stesso tempo possono implementare un numero arbitrario di interfacce. Questo previene possibili conflitti o ambiguità derivanti dall'ereditarietà multipla completa, ma conserva comunque la flessibilità consentendo ad un tipo di poter avere più interfacce di routine. Le interfacce possono essere usate anche con estensioni private, ma in quel caso la vista completa e quella parziale del tipo devono essere conformi rispetto alle interfacce implementate, per cui non è possibile aggiungere o togliere interfacce nel completamento privato della dichiarazione.

### Tipi controllati

In Ada non esiste il concetto di costruttore, ma è possibile sostituirne le caratteristiche funzionali usando un tipo controllato. Un tipo controllato è un tipo che estende Ada.Finalization.Controlled (oppure Ada.Finalization.Limited_Controlled per i tipi controllati e limitati), per il quale è possibile eseguire l'override di tre procedure (due nel caso dei tipi limitati, dove manca la procedura Adjust):
La procedura Initialize viene eseguita sull'oggetto subito dopo la creazione e può svolgere le funzionalità di inizializzazione tipicamente delegate ad un costruttore, la procedura Adjust viene eseguita subito dopo un'assegnazione (per cui non è disponibile per i tipi Limited_Controlled) e può fungere da costruttore di copia, mentre la procedura Finalize viene eseguita immediatamente prima della deallocazione di un oggetto, e funge da distruttore.
Per ragioni storiche, i tipi Ada.Finalization.Controlled e Ada.Finalization.Limited_Controlled non sono interfacce (aggiunte solo in Ada 2005) ma tipi astratti, per cui non è possibile definire un tipo che sia controllato e che contemporaneamente erediti l'implementazione di un tipo non controllato.