La cancellazione del tipo è buona
Atteniamoci ai fatti
Molte delle risposte finora riguardano eccessivamente l'utente Twitter. È utile rimanere concentrati sui messaggi e non sul messenger. C'è un messaggio abbastanza coerente anche solo con gli estratti menzionati finora:
È divertente quando gli utenti Java si lamentano della cancellazione del tipo, che è l'unica cosa che Java ha corretto, ignorando tutte le cose che ha sbagliato.
Ottengo enormi vantaggi (ad es. Parametricità) e costo nullo (il presunto costo è un limite dell'immaginazione).
new T è un programma rotto. È isomorfico all'affermazione "tutte le proposizioni sono vere". Non sono grande in questo.
Un obiettivo: programmi ragionevoli
Questi tweet riflettono una prospettiva che non è interessata a sapere se possiamo far fare qualcosa alla macchina , ma più se possiamo ragionare che la macchina farà qualcosa che vogliamo realmente. Un buon ragionamento è una prova. Le dimostrazioni possono essere specificate in notazione formale o qualcosa di meno formale. Indipendentemente dal linguaggio delle specifiche, devono essere chiari e rigorosi. Le specifiche informali non sono impossibili da strutturare correttamente, ma sono spesso difettose nella programmazione pratica. Finiamo con rimedi come test automatizzati ed esplorativi per compensare i problemi che abbiamo con il ragionamento informale. Questo non vuol dire che il test sia intrinsecamente una cattiva idea, ma il citato utente di Twitter suggerisce che esiste un modo molto migliore.
Quindi il nostro obiettivo è avere programmi corretti su cui ragionare in modo chiaro e rigoroso in un modo che corrisponda a come la macchina eseguirà effettivamente il programma. Questo, però, non è l'unico obiettivo. Vogliamo anche che la nostra logica abbia un certo grado di espressività. Ad esempio, c'è solo così tanto che possiamo esprimere con la logica proposizionale. È bello avere una quantificazione universale (∀) ed esistenziale (∃) da qualcosa come la logica del primo ordine.
Utilizzo di sistemi di tipo per il ragionamento
Questi obiettivi possono essere affrontati molto bene dai sistemi di tipo. Ciò è particolarmente chiaro a causa della corrispondenza Curry-Howard . Questa corrispondenza è spesso espressa con la seguente analogia: i tipi stanno ai programmi come i teoremi stanno alle dimostrazioni.
Questa corrispondenza è piuttosto profonda. Possiamo prendere espressioni logiche e tradurle attraverso la corrispondenza ai tipi. Quindi, se abbiamo un programma con lo stesso tipo di firma che compila, abbiamo dimostrato che l'espressione logica è universalmente vera (una tautologia). Questo perché la corrispondenza è bidirezionale. La trasformazione tra i mondi tipo / programma e teorema / dimostrazione è meccanica e in molti casi può essere automatizzata.
Curry-Howard si adatta bene a ciò che vorremmo fare con le specifiche per un programma.
I sistemi di tipi sono utili in Java?
Anche con una comprensione di Curry-Howard, alcune persone trovano facile ignorare il valore di un sistema di tipi, quando
- è estremamente difficile lavorare con
- corrisponde (attraverso Curry-Howard) a una logica con espressività limitata
- è rotto (che arriva alla caratterizzazione dei sistemi come "deboli" o "forti").
Per quanto riguarda il primo punto, forse gli IDE rendono il sistema di tipi di Java abbastanza facile da lavorare (questo è altamente soggettivo).
Per quanto riguarda il secondo punto, Java sembra quasi corrispondere a una logica del primo ordine. I generici danno l'uso del sistema di tipi equivalente alla quantificazione universale. Sfortunatamente, i caratteri jolly ci danno solo una piccola frazione di quantificazione esistenziale. Ma la quantificazione universale è un buon inizio. È bello poter dire che funzioni per il List<A>
lavoro universalmente per tutti gli elenchi possibili perché A è completamente libero da vincoli. Questo porta a ciò di cui parla l'utente Twitter rispetto alla "parametricità".
Un articolo spesso citato sulla parametricità è i Teoremi di Philip Wadler gratuitamente! . La cosa interessante di questo articolo è che dalla sola firma del tipo, possiamo provare alcune invarianti molto interessanti. Se dovessimo scrivere test automatizzati per questi invarianti, sprecheremmo molto tempo. Ad esempio, per List<A>
, dalla sola firma del tipo perflatten
<A> List<A> flatten(List<List<A>> nestedLists);
possiamo ragionarlo
flatten(nestedList.map(l -> l.map(any_function)))
≡ flatten(nestList).map(any_function)
Questo è un semplice esempio, e probabilmente puoi ragionarci in modo informale, ma è ancora più bello quando otteniamo tali prove formalmente gratuitamente dal sistema di tipi e controllate dal compilatore.
La mancata cancellazione può portare ad abusi
Dal punto di vista dell'implementazione del linguaggio, i generici di Java (che corrispondono ai tipi universali) giocano molto pesantemente sulla parametricità utilizzata per ottenere prove su ciò che fanno i nostri programmi. Questo arriva al terzo problema menzionato. Tutti questi guadagni di prova e correttezza richiedono un sistema di tipo suono implementato senza difetti. Java ha sicuramente alcune caratteristiche del linguaggio che ci permettono di mandare in frantumi il nostro ragionamento. Questi includono ma non sono limitati a:
- effetti collaterali con un sistema esterno
- riflessione
I farmaci generici non cancellati sono per molti versi legati alla riflessione. Senza cancellazione ci sono informazioni di runtime che vengono trasportate con l'implementazione che possiamo usare per progettare i nostri algoritmi. Ciò significa che staticamente, quando ragioniamo sui programmi, non abbiamo il quadro completo. La riflessione minaccia gravemente la correttezza di tutte le prove su cui ragioniamo staticamente. Non è un caso che la riflessione porti anche a una serie di difetti complicati.
Quindi quali sono i modi in cui i farmaci generici non cancellati potrebbero essere "utili?" Consideriamo l'utilizzo menzionato nel tweet:
<T> T broken { return new T(); }
Cosa succede se T non ha un costruttore senza argomenti? In alcune lingue ciò che ottieni è nullo. O forse salti il valore nullo e vai direttamente a sollevare un'eccezione (a cui comunque i valori nulli sembrano portare). Poiché il nostro linguaggio è completo di Turing, è impossibile ragionare su quali chiamate broken
coinvolgeranno tipi "sicuri" con costruttori senza argomenti e quali no. Abbiamo perso la certezza che il nostro programma funzioni universalmente.
Cancellare significa che abbiamo ragionato (quindi cancelliamo)
Quindi, se vogliamo ragionare sui nostri programmi, consigliamo vivamente di non utilizzare caratteristiche linguistiche che minacciano fortemente il nostro ragionamento. Una volta fatto ciò, perché non eliminare i tipi in fase di esecuzione? Non sono necessari. Possiamo ottenere un po 'di efficienza e semplicità con la soddisfazione che nessun cast fallirà o che i metodi potrebbero mancare durante l'invocazione.
La cancellazione incoraggia il ragionamento.