Tipizzazione dinamica

todo Ogni dato è caratterizzato da tre proprietà:

  1. Valore
  2. Tipo
  3. Occupazione di memoria

Può essere che l’occupazione di memoria determini il tipo di dati (soprattutto per tipi primitivi)

Il type checking è l’insieme di azioni in base alle quali l’interprete o l’interprete:

  1. determina il tipo di dato
  2. effettua alcuni controlli sulla possibilità di effetturare alcune operazioni su quel tipo di dato.

Operazioni di type checking che stabiliscono il tipo di dato associato e ne verificano i vincoli possono avvenire sia a tempo di compilazione (compile time) che a tempo di esecuzione (run time).

Type checking statico

C, C++, Go, Pascal... Il type checking è effettuato a tempo di compilazione. È visto come sicuro perché permette di trovare gli errori con anticipo. Garantisce migliori prestazioni, perché riesce a ottimizzare meglio l’eseguibile, e elimina tutta la logica di checking a run time.

Type checking dinamico

Javascript, Python, PHP, Perl... La maggior parte dei controlli sono eseguiti a run time. Le dichiarazioni di tipo non sono necessarie. Le variabili possono cambiare tipo a runtime.

Più semplice da scrivere, ma si paga con un maggior overhead a runtime.

Facendo il check a runtime, c’è molto più spazio per errori, dal momento che non si può prevedere l’input. Quindi bisogna integrare bene i test, effettuare controlli sui dati e implementare meccanismi sofisticati per la gestione di errori.

Type checking dinamico

Si possono implementare sia type checking dinamico che statico. Ad esempio Java fa uso del dinamico per effetturare controlli su binding tra classi, e dello statico per tutto il resto.

Pro tipizzazione

Ci sono molti pro che si porta la tipizzazione.

Sicurezza

La tipizzazione permette di scoprire codice illecito o privo di senso. Se effettuata a tempo di compilazione permette di imbattersi in errori inaspettati a run time, cosa molto pericolosa.

Ottimizzazione

Se applicato a compile time, le operazioni di type checking possono fornire suggerimenti per eventuali ottimizzazioni durante la generazione del codice.

Astrazione

Il tipo di dato permette di costruire programmi ad un livello di astrazione molto elevato, e quindi più facilmente.

Documentazione

Tipi di dato sono più semplici da documentare, per far capire più semplicemente cosa si voleva intendere.

Modularità

L’uso appropriato dei tipi di dato costituisce uno strumento semplice e robusto per definire interfacce di programmazione (API).

Tipizzazione

Un altro aspetto della tipizzazione è il rispetto dei vincoli quando due oggetti di diverso tipo devono essere combinati.

Tipizzazione debole

Non impedisce operazioni incongruenti. Fa uso di operatori di conversione (casting implicito), per rendere omogenei gli operangi.

Tipizzazione forte - strongly typed

Non esiste un linguaggio a tipizzazione completamente forte, perché sarebbe inutilizzabile.

Impone forti regole e impedisce utilizzi incorretti dei tipi di dato specificati. Ad esempio impedisce operazioni aritmetiche tra numeri e caratteri. Python e Java sono fortemente tipizzati.

x = 5
y = "37"
print(x+y)		# errore, perché non esegue casting implicito
 
# bisogna fare casting esplicito 
# da stringa a intero o da intero a stringa
print(x+int(y)) # 42
print(str(x)+y) # 537

Safe/unsafe

Un linguaggio adotta una tipizzazione safe se non permette ad una operazione di casting implicito di produrre un crash. $x=5; $y="37"; $ = $x + $y Nella stringa sopra, PERL esegue il casting implicito di $y, che viene convertito in un int con valore 37. Se la stringa fosse stata una parola, il valore sarebbe stato 0.

La tipizzazione unsafe, al contrario, può produrre un crash se l’operazione non è consentita. int x= 5; char y[ ] = “37”; char *z = y + x; In C, questa riga genera uno z, che non è altro che l’indirizzo di memoria di y spostato in avanti di 5 byte. Se si prova a dereferenziare z, e l’indirizzo di memoria non appartiene al processo, risulta in un crash.

Polimorfismo

Capacità di differenziare il comportamento di parti di codice in base all’entità a cui sono applicati (code reuse).

Polimorfismo è solitamente legato all’ereditarietà, che garantisce che le classi possano avere una stessa interfaccia. L’overriding dei metodi, quindi, permette che gli oggetti delle sottoclassi rispondano diversamente agli stessi utilizzi (metodi polimorfi).

Per applicare il polimorfismo si eseguono alcuni passi:

  • si individua la classe di appartenenza dell’oggetto e si cerca la signature valida per il metodo
  • in caso di mancata individuazione si ripete il controllo per tutte le superclassi (operation dispatching)

Solitamente la ricerca viene fatta a compile time, perché ritenuta troppo costosa a run time. In java e nei linguaggi dinamici la ricerca viene fatta a run time. Nei linguaggi dinamici c’è l’alternativa chiamata duck typing.

Il polimorfismo a livello di tipo di dato permette di:

  • differenziare il comportamento dei metodi a seconda del tipo di dato
  • evitare di dover definire classe/metodo/struttura per ogni tipo di dato

Tutto questo è funzionale per il riuso del codice.

def calcola(a,b,c):
    return (a+b)*c

Questo metodo può essere chiamato con almeno 4 tipi di dato differenti, ovvero int, float, string e array di numeri.

types