In che modo la tipizzazione statica è davvero utile nei progetti più grandi?

9

Mentre curioso sulla pagina principale del sito di un linguaggio di programmazione di script, ho riscontrato questo passaggio:

When a system gets too big to keep in your head, you can add static types.

Questo mi ha fatto ricordare che in molte religioni guerre tra linguaggi statici compilati (come Java) e linguaggi dinamici e interpretati (principalmente Python perché è più usato, ma è un "problema" condiviso tra la maggior parte dei linguaggi di scripting), uno dei si lamenta che i fan delle lingue tipizzate in modo statico su lingue tipicamente dinamiche è che non si adattano bene ai progetti più grandi perché "un giorno dimenticherete il tipo restituito di una funzione e dovrete cercarlo, mentre con staticamente lingue digitate, tutto è esplicitamente dichiarato ".

Non ho mai capito affermazioni come questa. Per essere onesti, anche se dichiari il tipo di ritorno di una funzione, puoi e lo dimenticherai dopo aver scritto molte righe di codice, e dovrai comunque tornare alla riga in cui è stato dichiarato utilizzando la funzione di ricerca di il tuo editor di testo per controllarlo.

Come aggiunta, siccome le funzioni sono dichiarate con type funcname()... , senza conoscere type dovrai cercare su ogni riga in cui viene chiamata la funzione, perché conosci solo funcname , mentre in Python e simili devi solo cercare def funcname o function funcname che si verifica solo una volta, alla dichiarazione.

Inoltre, con REPLs è banale testare una funzione per il suo tipo di ritorno con input diversi, mentre con linguaggi tipizzati staticamente è necessario aggiungere alcune linee di codice e ricompilare tutto solo per conoscere il tipo dichiarato.

Quindi, oltre a conoscere il tipo di ritorno di una funzione che chiaramente non è un punto di forza dei linguaggi tipizzati in modo statico, in che modo la tipizzazione statica è davvero utile nei progetti più grandi?

    
posta user6245072 16.07.2016 - 10:26
fonte

6 risposte

21

More over, with REPLs it's trivial to test a function for it's return type with different inputs

Non è banale. Non è affatto banale . È banale farlo con funzioni banali.

Ad esempio, potresti banalmente definire una funzione in cui il tipo di ritorno dipende interamente dal tipo di input.

getAnswer(v) {
 return v.answer
}

In questo caso, getAnswer in realtà non ha un singolo tipo di ritorno. Non esiste un test che tu possa mai scrivere che chiama questo con un input di esempio per imparare qual è il tipo di ritorno. sempre dipenderà dall'argomento effettivo. In fase di runtime.

E questo non include nemmeno funzioni che, ad esempio, eseguono ricerche nel database. O fai le cose in base all'input dell'utente. Oppure cerca variabili globali, che sono ovviamente di tipo dinamico. O cambiare il loro tipo di ritorno in casi casuali. Per non parlare della necessità di testare ogni singola funzione manualmente ogni volta.

getAnswer(x, y) {
   if (x + y.answer == 13)
       return 1;
   return "1";
}

Fondamentalmente, provare il tipo di ritorno della funzione nel caso generale è letteralmente matematicamente impossibile (Halting Problem). Il solo modo di garantire il tipo restituito è di limitare l'input in modo che la risposta a questa domanda non rientri nel dominio del problema di interruzione non consentendo la selezione di programmi non provabili, e questo è ciò che fa la tipizzazione statica.

As an addition, as functions are declared with type funcname()..., whitout knowing type you will have to search over each line in which the function is called, because you only know funcname, while in Python and the like you coud just search for def funcname or function funcname which only happens once, at the declaration.

I linguaggi tipizzati staticamente hanno cose chiamate "strumenti". Sono programmi che ti aiutano a fare cose con il tuo codice sorgente. In questo caso, fare semplicemente clic con il tasto destro del mouse e Vai a definizione, grazie a Resharper. Oppure usa la scorciatoia da tastiera. O semplicemente il mouse sopra e mi dirà quali sono i tipi coinvolti. Non mi interessa minimamente di file grepping. Un editor di testo a parte è uno strumento patetico per modificare il codice sorgente del programma.

Dalla memoria, def funcname non sarebbe sufficiente in Python, poiché la funzione potrebbe essere nuovamente assegnata arbitrariamente. Oppure potrebbe essere dichiarato ripetutamente in più moduli. O in classe. Ecc.

and you will still have to return to the line in which it's declared using the search function of your text editor to check it.

La ricerca di file per il nome della funzione è un'operazione terribile e primitiva che non dovrebbe mai essere richiesta. Ciò rappresenta un errore fondamentale dell'ambiente e degli strumenti. Il fatto che tu possa anche considerare di aver bisogno di una ricerca di testo in Python è un punto enorme contro Python.

    
risposta data 17.07.2016 - 00:33
fonte
19

Pensa a un progetto con molti programmatori, che è cambiato nel corso degli anni. Devi mantenere questo. C'è una funzione

getAnswer(v) {
 return v.answer
}

Che diamine fa? Cos'è v ? Da dove viene l'elemento answer ?

getAnswer(v : AnswerBot) {
  return v.answer
}

Ora abbiamo altre informazioni -; ha bisogno di un tipo di AnswerBot .

Se andiamo a una lingua basata sulla classe possiamo dire

class AnswerBot {
  var answer : String
  func getAnswer() -> String {
    return answer
  }
}

Ora possiamo avere una variabile di tipo AnswerBot e chiamare il metodo getAnswer e tutti sanno cosa fa. Eventuali modifiche vengono rilevate dal compilatore prima che venga eseguito qualsiasi test di runtime. Ci sono molti altri esempi ma forse questo ti dà l'idea?

    
risposta data 16.07.2016 - 10:48
fonte
11

Sembra che tu abbia alcuni malintesi su come lavorare con progetti statici di grandi dimensioni che potrebbero offuscare il tuo giudizio. Ecco alcuni suggerimenti:

even if you declare the return type of a function, you can and will forget it after you've written many lines of code, and you will still have to return to the line in which it's declared using the search function of your text editor to check it.

La maggior parte delle persone che lavorano con lingue tipizzate staticamente utilizzano un IDE per la lingua o un editor intelligente (come vim o emacs) che ha integrazione con strumenti specifici della lingua. Di solito c'è un modo veloce per trovare il tipo di una funzione in questi strumenti. Ad esempio, con Eclipse su un progetto Java, ci sono due modi in cui normalmente si trova il tipo di un metodo:

  • Se voglio usare un metodo su un altro oggetto piuttosto che su "questo", scrivo un riferimento e un punto (ad esempio someVariable. ) ed Eclipse cerca il tipo di someVariable e fornisce un elenco a discesa di tutti i metodi definiti in quel tipo; mentre scorro l'elenco, il tipo e la documentazione di ciascuno vengono visualizzati mentre è selezionato. Nota che questo è molto difficile da ottenere con un linguaggio dinamico, perché è difficile (o in alcuni casi impossibile) per l'editore determinare quale sia il tipo di someVariable , quindi non può generare facilmente l'elenco corretto. Se voglio usare un metodo su this posso semplicemente premere ctrl + spazio per ottenere la stessa lista (anche se in questo caso non è difficile da ottenere per le lingue dinamiche).
  • Se ho già un riferimento scritto su un metodo specifico, posso spostare il cursore del mouse su di esso e il tipo e la documentazione per il metodo sono visualizzati in un suggerimento.

Come puoi vedere, questo è in qualche modo migliore degli strumenti tipici disponibili per i linguaggi dinamici (non che questo sia impossibile nelle lingue dinamiche, in quanto alcuni hanno funzionalità IDE piuttosto buone - smalltalk è uno che salta alla mente - ma è più difficile per un linguaggio dinamico e quindi meno probabile che sia disponibile).

As an addition, as functions are declared with type funcname()..., whitout knowing type you will have to search over each line in which the function is called, because you only know funcname, while in Python and the like you coud just search for def funcname or function funcname which only happens once, at the declaration.

Gli strumenti di linguaggio statico in genere forniscono funzionalità di ricerca semantica, cioè possono trovare la definizione e i riferimenti a simboli particolari con precisione, senza la necessità di eseguire una ricerca di testo. Ad esempio, utilizzando Eclipse per un progetto Java, è possibile evidenziare un simbolo nell'editor di testo e fare clic con il pulsante destro del mouse e scegliere "vai a definizione" o "trova riferimenti" per eseguire una di queste operazioni. Non è necessario cercare il testo di una definizione di funzione, perché il tuo editor sa già esattamente dove si trova.

Tuttavia, l'opposto è che la ricerca di una definizione di metodo in base al testo non funziona molto bene in un grande progetto dinamico come suggerisci, poiché potrebbero esserci facilmente più metodi con lo stesso nome in tale progetto, e tu probabilmente non dispongono di strumenti prontamente disponibili per disambiguare quale di essi stai invocando (perché tali strumenti sono difficili da scrivere nel migliore dei casi o impossibili nel caso generale), quindi dovrai farlo a mano.

More over, with REPLs it's trivial to test a function for it's return type with different inputs

Non è impossibile avere un REPL per una lingua tipizzata in modo statico. Haskell è l'esempio che mi viene in mente, ma ci sono REPL anche per altri linguaggi tipizzati staticamente. Ma il punto è che non è necessario eseguire codice per trovare il tipo di ritorno di una funzione in un linguaggio statico - può essere determinato dall'esame senza bisogno di eseguire nulla.

while with staticly typed languages you would need to add some lines of code and recompile everything just to know the type declared.

Le probabilità sono anche se hai bisogno di farlo, non dovresti ricompilare tutto . La maggior parte dei linguaggi statici moderni dispone di compilatori incrementali che compilano solo la piccola parte del codice che è stata modificata, in modo da ottenere un feedback quasi istantaneo per errori di tipo se ne crei uno. Eclipse / Java, ad esempio, evidenzierà gli errori di tipo mentre stai ancora digitandoli .

    
risposta data 17.07.2016 - 14:12
fonte
6
  1. Perché i controllori statici sono più facili per le lingue tipizzate in modo statico.
    • Al minimo, senza funzionalità di linguaggio dinamico, se si compila, in fase di esecuzione non ci sono funzioni non risolte. Questo è comune nei progetti ADA e C sui microcontrollori. (I programmi di microcontrollori diventano grandi a volte ... come centinaia di kloc grandi.)
  2. I controlli di riferimento di compilazione statici sono un sottoinsieme di invarianti di funzione, che in un linguaggio statico possono anche essere verificati in fase di compilazione.
  3. Le lingue statiche di solito hanno più trasparenza referenziale. Il risultato è che un nuovo sviluppatore può immergersi in un singolo file e capire cosa sta succedendo, e correggere un bug o aggiungere una piccola funzionalità senza dover conoscere tutte le strane cose nella base di codice.

Confronta con dire, javascript, Ruby o Smalltalk, dove gli sviluppatori ridefiniscono le funzionalità linguistiche di base in fase di esecuzione. Questo rende la comprensione del grande progetto più difficile.

I progetti più grandi non hanno solo più persone, hanno più tempo. Abbastanza tempo per tutti da dimenticare, o andare avanti.

Aneddoticamente, un mio conoscente ha una programmazione sicura di "Job For Life" in Lisp. Nessuno eccetto il team può capire il codice base.

    
risposta data 17.07.2016 - 00:21
fonte
4

I never understood statements like this one. To be honest, even if you declare the return type of a function, you can and will forget it after you've written many lines of code, and you will still have to return to the line in which it's declared using the search function of your text editor to check it.

Non si tratta di te che dimentichi il tipo di reso - questo succederà sempre. Si tratta dello strumento in grado di farti sapere che hai dimenticato il tipo di reso.

As an addition, as functions are declared with type funcname()..., whitout knowing type you will have to search over each line in which the function is called, because you only know funcname, while in Python and the like you could just search for def funcname or function funcname which only happens once, at the declaration.

Questa è una questione di sintassi, che non è completamente correlata alla tipizzazione statica.

La sintassi della famiglia C è davvero ostile quando si vuole cercare una dichiarazione senza avere strumenti specializzati a vostra disposizione. Altre lingue non hanno questo problema. Vedi sintassi della dichiarazione di Rust:

fn funcname(a: i32) -> i32

More over, with REPLs it's trivial to test a function for it's return type with different inputs, while with statically typed languages you would need to add some lines of code and recompile everything just to know the type declared.

Qualsiasi lingua può essere interpretata e qualsiasi lingua può avere un REPL.

So, other than to know the return type of a function which clearly isn't a strong point of statically typed languages, how is static typing really helpful in bigger projects?

Risponderò in modo astratto.

Un programma consiste in varie operazioni e tali operazioni sono disposte come sono a causa di alcuni presupposti dello sviluppatore.

Alcune ipotesi sono implicite e altre sono esplicite. Alcuni presupposti riguardano un'operazione vicina a loro, alcuni riguardano un'operazione che li allontana. Un'assunzione è più facile da identificare quando è espressa in modo esplicito e il più vicino possibile ai luoghi in cui è importante il suo valore di verità.

Un bug è la manifestazione di un'ipotesi che esiste nel programma ma che non regge per alcuni casi. Per rintracciare un bug, dobbiamo identificare l'ipotesi errata. Per rimuovere il bug, dobbiamo rimuovere tale ipotesi dal programma o modificare qualcosa in modo tale che l'assunzione sia effettivamente valida.

Vorrei classificare le ipotesi in due tipi.

Il primo tipo sono le ipotesi che possono o meno tenere, a seconda degli input del programma. Per identificare un'assunzione errata di questo tipo, dobbiamo cercare nello spazio di tutti i possibili input del programma. Utilizzando ipotesi plausibili e pensiero razionale, possiamo restringere il problema e cercare in uno spazio molto più piccolo. Ma ancora, mentre un programma cresce anche un po ', il suo spazio di input iniziale cresce ad un ritmo enorme - al punto in cui può essere considerato infinito per tutti gli scopi pratici.

Il secondo tipo sono le ipotesi che sicuramente valgono per tutti gli input, o sono sicuramente errate per tutti gli input. Quando identifichiamo un'assunzione di questo tipo come errata, non abbiamo nemmeno bisogno di eseguire il programma o testare alcun input. Quando identifichiamo un'assunzione di questo tipo come corretta, abbiamo un sospetto in meno di cui preoccuparsi quando stiamo rintracciando un bug ( qualsiasi bug). Pertanto, c'è un valore nell'avere quante più ipotesi possibili appartengono a questo tipo.

Per inserire un'ipotesi nella seconda categoria (sempre vera o sempre falsa, indipendente dagli input), abbiamo bisogno di una quantità minima di informazioni disponibili nel luogo in cui viene formulata l'ipotesi. Attraverso il codice sorgente di un programma, le informazioni diventano obsolete rapidamente (ad esempio, molti compilatori non eseguono analisi interprocedurali, il che rende ogni chiamata un limite difficile per la maggior parte delle informazioni). Abbiamo bisogno di un modo per mantenere le informazioni richieste fresche (valide e vicine).

Un modo è quello di avere la fonte di queste informazioni il più vicino possibile al luogo in cui verrà consumata, ma che può essere poco pratica per la maggior parte dei casi d'uso. Un altro modo è quello di ripetere frequentemente le informazioni, rinnovandone la pertinenza attraverso il codice sorgente.

Come puoi già intuire, i tipi statici sono esattamente questo: i beacon di informazioni sul tipo sparsi sul codice sorgente. Queste informazioni possono essere utilizzate per mettere la maggior parte delle ipotesi sulla correttezza del tipo nella seconda categoria, il che significa che quasi tutte le operazioni possono essere classificate come sempre corrette o sempre errate rispetto alla compatibilità dei tipi.

Quando i nostri tipi non sono corretti, l'analisi ci fa risparmiare tempo portando il bug alla nostra attenzione presto piuttosto tardi. Quando i nostri tipi sono corretti, l'analisi ci fa risparmiare tempo assicurandoci che quando si verifica un errore, possiamo immediatamente escludere errori di tipo.

    
risposta data 16.07.2016 - 22:37
fonte
3

Ricordi il vecchio adagio "spazzatura in, spazzatura fuori", beh, questo è ciò che la tipizzazione statica aiuta a prevenire. Non è una panacea universale, ma la severità su quale tipo di dati una routine accetta e restituisce significa che hai la certezza che stai lavorando correttamente con esso.

Quindi una routine getAnswer che restituisce un intero non sarà utile quando si tenta di utilizzarla in una chiamata basata su stringhe. La tipizzazione statica sta già dicendo di stare attento, che probabilmente stai facendo un errore. (e certo, puoi quindi sovrascriverlo, ma dovresti sapere esattamente che cosa stai facendo e specificarlo nel codice usando un cast. In generale però, non vuoi farlo - hacking in a la spina tonda in un buco quadrato non funziona mai bene alla fine)

Ora puoi andare oltre usando tipi complessi, creando una classe che ha una funzionalità di base, puoi iniziare a farli passare e improvvisamente ottieni molta più struttura nel tuo programma. I programmi strutturati sono quelli che sono molto più facili da eseguire correttamente e anche da mantenere.

    
risposta data 18.07.2016 - 22:51
fonte

Leggi altre domande sui tag