L'ho sempre visto più come una questione di convenienza, piuttosto che sul fatto che un algoritmo possa o non possa essere espresso affatto. Se volessi davvero eseguire programmi come quello inventato di Mitchell, scriverei semplicemente il simulatore Turing Machine appropriato nel mio linguaggio tipicamente statico.
Il trucco con un sistema di tipo statico è offrire i giusti tipi di flessibilità solo nei casi in cui la flessibilità consente di scrivere codice che è più facilmente gestibile.
Ecco alcuni esempi di tecniche di strutturazione del programma che a volte sono ritenute più facili da gestire in linguaggi dinamici rispetto a quelli tipicamente statici.
Generici e contenitori
Nei linguaggi tipicamente statici prima di ML (c. 1973) e CLU (c. 1974) non era difficile creare un albero di stringhe rosso-nero, un albero di numeri rosso-nero, un albero di galleggianti rosso-nero, o un albero rosso-nero di elementi di tipo specifico Foo
. Tuttavia, è stato difficile (forse impossibile) creare un'unica implementazione di un albero rosso-nero che è stato sia controllato staticamente che in grado di gestire uno di questi tipi di dati. I modi per aggirare il problema erano (1) per uscire completamente dal sistema dei tipi (per esempio: usandovoid *
in C), (2) per scrivere un qualche tipo di preprocessore macro e poi scrivere macro che producono il codice per ogni tipo specifico che si desidera o (3) utilizzare l'approccio Lisp / Smalltalk (e Java) per verificare il tipo di estratto oggetto dinamicamente.
ML e CLU hanno introdotto la nozione, rispettivamente, di tipi parametrici inferiti ed esplicitamente dichiarati (statici), che consentono di scrivere tipi di container generici, tipizzati staticamente.
Sottotipo polimorfismo
Nei linguaggi tipizzati staticamente prima di Simula67 (c. 1967) e Hope (c. 1977) non era possibile effettuare spedizioni dinamiche e verificare staticamente che il caso fosse coperto per ogni sottotipo. Molte lingue hanno una qualche forma di unioni con tag , ma era la responsabilità del programmatore per assicurarsi che i loro case
o switch
dichiarazioni, o le loro tabelle di salto, coperti ogni possibile etichetta.
Le lingue che seguono il modello Simula (C ++, Java, C #, Eiffel) forniscono alle classi astratte una sottoclasse in cui il compilatore può verificare che ciascuna sottoclasse abbia implementato tutti i metodi dichiarati dalla classe genitore. Le lingue che seguono il modello Hope (tutte le varianti ML, da SML / NJ a Haskell) hanno sottotipi algebrici in cui il compilatore può verificare che ogni typecase
istruzione copra tutti i sottotipi.
Monkey Patching e programmazione orientata agli aspetti
I sistemi di tipo dinamico rendono molto più semplice una varietà di tecniche di prototipazione. Nei linguaggi in cui i tipi sono rappresentati da mappe hash da stringhe a funzioni (ad esempio, Python, Javascript, Ruby) è abbastanza facile cambiare globalmente il comportamento di ogni modulo che si basa su un tipo particolare, semplicemente modificando dinamicamente la mappa hash che rappresenta quella genere.
Mentre ci sono modi ovvi in cui il patching delle scimmie può essere usato per rendere i programmi più difficili da mantenere, ci sono anche modi in cui può effettivamente essere usato per "bene" piuttosto che "male". In particolare con la programmazione orientata all'aspetto, è possibile utilizzare tecniche simili a quelle di scimmia per fare cose come modificare il tipo di file in modo da puntare a un file system virtualizzato, consentendo la costruzione di infrastrutture di testing di unità "libere" o modificare tipi di eccezione semplici in modo che stampare i messaggi di registro ogni volta che vengono rilevati per una migliore debuggabilità.
A differenza del polimorfismo dei generici e dei sottotipi in cui le idee chiave per il controllo statico erano disponibili negli anni '70, il controllo statico per la programmazione orientata all'aspetto è (penso) un'area di ricerca attiva. Non ne so molto se non che esiste un linguaggio chiamato AspectJ dal 2001 circa.