Frasi come "tipizzazione statica" e "tipizzazione dinamica" sono molto diffuse e le persone tendono ad usare definizioni leggermente diverse, quindi iniziamo chiarendo cosa intendiamo.
Prendi in considerazione un linguaggio con tipi statici che vengono controllati in fase di compilazione. Ma supponiamo che un errore di tipo generi solo un avviso non fatale e, in fase di esecuzione, tutto è tipizzato a forma di anatra. Questi tipi statici sono solo per comodità del programmatore e non influiscono sul codegen. Ciò dimostra che la tipizzazione statica non impone di per sé alcuna limitazione e non si esclude a vicenda con la tipizzazione dinamica. (Objective-C è molto simile a questo.)
Ma la maggior parte dei sistemi di tipo statico non si comporta in questo modo. Esistono due proprietà comuni dei sistemi di tipo statico che possono imporre limitazioni:
Il compilatore potrebbe rifiutare un programma che contiene un errore di tipo statico.
Questa è una limitazione perché molti programmi sicuri di tipo contengono necessariamente un errore di tipo statico.
Ad esempio, ho uno script Python che deve essere eseguito come Python 2 e Python 3. Alcune funzioni hanno cambiato il tipo di parametro tra Python 2 e 3, quindi ho un codice come questo:
if sys.version_info[0] == 2:
wfile.write(txt)
else:
wfile.write(bytes(txt, 'utf-8'))
Un correttore di tipo statico Python 2 rifiuterebbe il codice Python 3 (e viceversa), anche se non sarebbe mai stato eseguito. Il mio programma di tipo sicuro contiene un errore di tipo statico.
Come altro esempio, considera un programma per Mac che vuole essere eseguito su OS X 10.6, ma sfrutta le nuove funzionalità di 10.7. I metodi 10.7 possono esistere o meno in fase di esecuzione ed è compito mio, il programmatore, rilevarli. Un verificatore di tipo statico è costretto a rifiutare il mio programma per garantire la sicurezza del tipo o ad accettarlo, insieme alla possibilità di produrre un errore di tipo (funzione mancante) in fase di esecuzione.
Il controllo statico del tipo presuppone che l'ambiente di runtime sia adeguatamente descritto dalle informazioni sul tempo di compilazione. Ma prevedere il futuro è pericoloso!
Ecco un'altra limitazione:
Il compilatore può generare codice che presuppone che il tipo di runtime sia di tipo statico.
Supponendo che i tipi statici siano "corretti" offre molte opportunità di ottimizzazione, ma queste ottimizzazioni possono essere limitanti. Un buon esempio sono gli oggetti proxy, ad esempio i telecomandi. Supponi di voler avere un oggetto proxy locale che inoltri le invocazioni di metodo a un oggetto reale in un altro processo. Sarebbe bello se il proxy fosse generico (quindi può mascherarsi come qualsiasi oggetto) e trasparente (in modo che il codice esistente non abbia bisogno di sapere che sta parlando con un proxy). Ma per fare ciò, il compilatore non può generare codice che presume che i tipi statici siano corretti, ad es. Facendo riferimento alle chiamate dei metodi in modo statico, poiché ciò non riuscirà se l'oggetto è effettivamente un proxy.
Esempi di tale remoting in azione includono NSXPCConnection di ObjC o TransparentProxy di C # (la cui implementazione ha richiesto alcune pessimizzazioni in fase di esecuzione - vedi qui per una discussione).
Quando il codegen non dipende dai tipi statici e hai servizi come l'inoltro dei messaggi, puoi fare un sacco di cose interessanti con oggetti proxy, debug, ecc.
Quindi questo è un campione di alcune delle cose che puoi fare se non sei tenuto a soddisfare un controllo di tipo. Le limitazioni non sono imposte dai tipi statici, ma dal controllo del tipo statico forzato.