Qual è il compromesso per l'inferenza di tipo?

27

Sembra che tutti i nuovi linguaggi di programmazione o almeno quelli diventati popolari utilizzino l'inferenza di tipo. Anche Javascript ha ottenuto tipi e inferenza di tipo attraverso varie implementazioni (Acrittico, dattiloscritto ecc.). Mi sembra fantastico ma mi chiedo se ci sono dei compromessi o perché diciamo che Java o le vecchie buone lingue non hanno inferenza di tipo

  • Quando dichiari una variabile in Vai senza specificarne il tipo (usando var senza un tipo o la sintassi: =), il tipo della variabile viene dedotto dal valore sul lato destro.
  • D consente di scrivere frammenti di codice di grandi dimensioni senza specificare in modo ridondante i tipi, come fanno i linguaggi dinamici. D'altra parte, l'inferenza statica deduce tipi e altre proprietà del codice, dando il meglio dei mondi statici e dinamici.
  • Il motore di inferenza del tipo in Rust è abbastanza intelligente. Fa più che guardare il tipo del valore r durante un'inizializzazione. Guarda anche come la variabile viene utilizzata in seguito per dedurne il tipo.
  • Swift utilizza l'inferenza del tipo per elaborare il tipo appropriato. L'inferenza di tipo consente a un compilatore di dedurre automaticamente il tipo di un'espressione particolare quando compila il codice, semplicemente esaminando i valori forniti.
posta The user with no hat 03.02.2015 - 03:45
fonte

6 risposte

39

Il sistema di tipi di Haskell è completamente deducibile (lasciando da parte la ricorsione polimorfica, alcuni lingua estensioni e la temuta limitazione del monomorfismo ), ma i programmatori forniscono ancora frequentemente il tipo annotazioni nel codice sorgente anche quando non ne hanno bisogno. Perché?

  1. Digitare le annotazioni come documentazione . Questo è particolarmente vero con tipi espressivi come quelli di Haskell. Dato il nome di una funzione e il suo tipo, in genere si può avere una buona ipotesi su ciò che fa la funzione, specialmente quando la funzione è parametrica- mente polimorfica.
  2. Digitare annotazioni può guidare lo sviluppo . Scrivere una firma di tipo prima di scrivere il corpo di una funzione sembra di tipo sviluppo basato sui test. Nella mia esperienza, una volta compilata una funzione Haskell, di solito funziona per la prima volta. (Ovviamente, questo non ovvia alla necessità di test automatici!)
  3. I tipi espliciti possono aiutarti a verificare le tue ipotesi . Quando cerco di capire un codice che funziona già, lo ricopro spesso con quelle che ritengo siano le annotazioni di tipo corrette. Se il codice è ancora compilato, so di averlo capito. In caso contrario, ho letto il messaggio di errore.
  4. Digitare le firme ti consente di specializzare le funzioni polimorfiche . Molto raramente, un'API è più espressiva o utile se determinate funzioni non sono polimorfiche. Il compilatore non si lamenterà se si assegna a una funzione un less di tipo generale di quanto verrebbe desunto. L'esempio classico è map :: (a -> b) -> [a] -> [b] . La sua forma più generale ( fmap :: Functor f => (a -> b) -> f a -> f b ) si applica a tutti Functor s, non solo liste. Ma si è ritenuto che map sarebbe più facile da capire per i principianti, quindi vive accanto al fratello maggiore.

Nel complesso, i lati negativi di un sistema con dattilografia statica, ma inferirabile, sono molto simili agli svantaggi della digitazione statica in generale, una discussione esauriente su questo sito e altri (Googling "svantaggi di tipizzazione statici" prendi centinaia di pagine di guerre di fiamma). Naturalmente, alcuni di detti svantaggi sono migliorati dalla minore quantità di annotazioni di tipo in un sistema deducibile. Inoltre, l'inferenza di tipo ha i suoi vantaggi: sviluppo guidato dal buco non sarebbe possibile senza inferenza di tipo.

Java * dimostra che un linguaggio che richiede troppe annotazioni di tipo diventa fastidioso, ma con troppi pochi si perdono i vantaggi che ho descritto sopra. Le lingue con inferenza di tipo opt-out colpiscono un piacevole equilibrio tra i due estremi.

* Anche Java, quel grande capro espiatorio, esegue una certa quantità di inferenza di tipo local . In un'istruzione come Map<String, Integer> = new HashMap<>(); , non è necessario specificare il tipo generico del costruttore. D'altra parte, i linguaggi in stile ML sono in genere globalmente inferrabili.

    
risposta data 03.02.2015 - 09:07
fonte
8

In C #, il tipo di inferenza si verifica in fase di compilazione, quindi il costo di runtime è zero.

Per motivi di stile, var viene utilizzato per situazioni in cui è scomodo o non necessario specificare manualmente il tipo. Linq è una di queste situazioni. Un altro è:

var s = new SomeReallyLongTypeNameWith<Several, Type, Parameters>(andFormal, parameters);

senza il quale dovresti ripetere il tuo nome di tipo veramente lungo (e i parametri di tipo) invece di dire semplicemente var .

Utilizza il nome effettivo del tipo quando l'esplicito migliora la chiarezza del codice.

Ci sono alcune situazioni in cui non è possibile utilizzare l'inferenza di tipo, come le dichiarazioni delle variabili membro i cui valori sono impostati in fase di costruzione o in cui si desidera veramente che intellisense funzioni correttamente (l'IDE di Hackerrank non intelleggerà i membri della variabile a meno che non si dichiari il digita esplicitamente).

    
risposta data 03.02.2015 - 03:56
fonte
7

Buona domanda!

  1. Poiché il tipo non è esplicitamente annotato, a volte può rendere il codice più difficile da leggere, portando a più bug. Se usato correttamente, rende il codice più pulito e più leggibile. Se sei un pessimista e pensi che la maggior parte dei programmatori siano cattivi (o lavori in cui la maggior parte dei programmatori sono cattivi), questa sarà una perdita netta.
  2. Mentre l'algoritmo di inferenza del tipo è relativamente semplice, non è libero . Questo genere di cose aumenta leggermente il tempo di compilazione.
  3. Poiché il tipo non è esplicitamente annotato, il tuo IDE non può indovinare cosa stai cercando di fare, danneggiamento del completamento automatico e simili helper durante il processo di dichiarazione.
  4. In combinazione con gli overload di funzioni, è possibile occasionalmente entrare in situazioni in cui l'algoritmo di inferenza del tipo non è in grado di decidere quale percorso intraprendere, portando a brutte annotazioni sullo stile di lancio. (Questo accade un po 'con la sintassi della funzione anonima di C # per esempio).

E ci sono più linguaggi esoterici che non possono fare la loro stranezza senza un'annotazione di tipo esplicita. Finora, non ce ne sono che io sappia che sono comuni / popolari / promettenti abbastanza da menzionare eccetto che per il passaggio.

    
risposta data 03.02.2015 - 04:31
fonte
4

It looks great to me but I'm wondering if there are any trade-offs or why let's say Java or the old good languages don't have type inference

Java sembra essere l'eccezione piuttosto che la regola qui. Anche il C ++ (che credo sia qualificato come "buon vecchio linguaggio" :)) supporta l'inferenza di tipo con la parola chiave auto dallo standard C ++ 11. Non funziona solo con la dichiarazione di variabili, ma anche come tipo di ritorno di funzione, che è particolarmente utile con alcune funzioni di template complesse.

La tipizzazione implicita e l'inferenza del tipo hanno molti buoni casi d'uso, e ci sono anche alcuni casi d'uso in cui davvero non dovresti farlo. Questo a volte è questione di gusti e anche oggetto di dibattito.

Ma ci sono indubbiamente buoni casi d'uso, è di per sé una ragione legittima per qualsiasi lingua per implementarlo. Inoltre, non è una funzionalità difficile da implementare, nessuna penalità di runtime e non influisce in modo significativo sul tempo di compilazione.

Non vedo un vero svantaggio nel dare un'opportunità allo sviluppatore di usare l'inferenza di tipo.

Alcuni rispondenti spiegano quanto spesso la digitazione esplicita sia buona, e hanno certamente ragione. Ma non supportare la scrittura implicita significherebbe che un linguaggio impone continuamente la digitazione esplicita.

Quindi il vero svantaggio è quando una lingua non supporta la scrittura implicita, perché con ciò afferma che non esiste alcun motivo legittimo per lo sviluppatore di usarlo, il che non è vero.

    
risposta data 03.02.2015 - 10:08
fonte
1

La principale distinzione tra un sistema di inferenza di tipo Hindley-Milner e l'inferenza di tipo Go-style è la direzione del flusso di informazioni. In HM, digita i flussi di informazioni avanti e indietro tramite l'unificazione; in Vai, digita solo i flussi di informazioni in avanti: calcola solo le sostituzioni in avanti.

L'inferenza di tipo HM è una splendida innovazione che funziona bene con i linguaggi funzionali tipizzati polimorficamente, ma gli autori di Go probabilmente sosterrebbero che prova a fare troppo:

  1. Il fatto che l'informazione fluisca sia in avanti che all'indietro significa che l'inferenza di tipo HM è un affare molto non locale; in assenza di annotazioni di tipo, ogni riga di codice nel tuo programma potrebbe contribuire alla digitazione di una singola riga di codice. Se hai solo una sostituzione diretta, sai che la fonte di un errore di tipo deve essere nel codice che precede il tuo errore; non il codice che viene dopo.

  2. Con l'inferenza di tipo HM, tendi a pensare in constraints : quando usi un tipo, limiti i possibili tipi che può avere. Alla fine della giornata, potrebbero esserci alcune variabili di tipo che vengono lasciate totalmente non vincolate. L'intuizione dell'inferenza di tipo HM è che, in realtà, questi tipi non contano, e quindi sono trasformati in variabili polimorfiche. Tuttavia, questo polimorfismo extra può essere indesiderabile per una serie di motivi. Innanzitutto, come alcune persone hanno sottolineato, questo polimorfismo in più potrebbe non essere desiderabile: HM conclude un falso, tipo polimorfico per alcuni codici fasulli, e porta a strani errori in seguito. In secondo luogo, quando un tipo viene lasciato polimorfico, questo può avere conseguenze sul comportamento di runtime. Ad esempio, un risultato intermedio eccessivamente polimorfico è la ragione per cui "mostra". leggere "è considerato ambiguo in Haskell; come altro esempio, i valori polimorfici devono essere valutati più volte per ogni tipo in cui vengono valutati, motivando la restrizione del monomorfismo.

risposta data 19.03.2016 - 03:35
fonte
-2

Fa male la leggibilità.

Confronta

var stuff = someComplexFunction()

vs

List<BusinessObject> stuff = someComplexFunction()

È particolarmente un problema quando si legge all'esterno di un IDE come su github.

    
risposta data 20.09.2017 - 01:37
fonte

Leggi altre domande sui tag