Cosa fanno i designer linguistici per decidere o dimostrare che una determinata funzionalità funziona correttamente?


11

Sono interessato al design del linguaggio e in generale posso ragionare facilmente su caratteristiche ampiamente conosciute (ad esempio eredità, polimorfismo, delegati, lambda, catture, raccolta dei rifiuti, eccezioni, generici, varianza, riflessione e così via), le loro interazioni in un linguaggio particolare, i modi in cui possono eventualmente essere implementati, i loro limiti, ecc.

Negli ultimi mesi, ho iniziato a leggere di Rust, che ha un sistema di proprietà che garantisce la sicurezza della memoria e la gestione deterministica delle risorse forzando la durata degli oggetti a essere verificata staticamente. Dal punto di vista di un semplice utente della lingua, ho potuto prendere il sistema quasi immediatamente.

Dal punto di vista di un designer linguistico, tuttavia, mi ci è voluto un po 'di tempo per capire perché le cose in Rust sono esattamente come sono. Non riuscivo immediatamente a capire il ragionamento alla base di alcune restrizioni del sistema di proprietà, fino a quando non mi costrinsi a inventare casi che avrebbero violato l'integrità di un sistema se non avesse avuto quegli aspetti.

La mia domanda principale non ha nulla a che fare con Rust e la sua proprietà in particolare - ma, se necessario, sentiti libero di usarlo come esempio nei tuoi commenti / risposte.

Quando i progettisti del linguaggio progettano una nuova funzionalità, quale metodologia o processo usano per decidere che la funzionalità funzioni correttamente?

Per "nuovo" intendo che non è qualcosa che è già stato testato nelle lingue esistenti (e quindi la maggior parte del lavoro è stata fatta da altri designer). Con "funziona correttamente" intendo che la funzione risolve correttamente il problema previsto ed è ragionevolmente a prova di proiettile. Con "ragionevolmente antiproiettile" intendo che nessun codice può essere scritto nella lingua o in un particolare sottoinsieme della lingua (ad esempio un sottoinsieme senza codice "non sicuro") che violerebbe l'integrità della funzione.

  • È un processo di prova ed errore, nel senso che ti viene in mente una semplice forma della funzione, quindi prova a trovare modi per violarla, quindi correggila se la violi con successo, quindi ripeti? E poi, quando non riesci a pensare ad altre possibili violazioni, speri che non sia rimasto nulla e lo chiami un giorno?

  • Oppure c'è un modo formale per dimostrare effettivamente (nel senso matematico della parola) che la tua funzione funziona e quindi usare quella prova per ottenere con sicurezza la funzione giusta (o principalmente giusta) dall'inizio?

(Devo dire che ho una formazione ingegneristica, non informatica. Quindi, se mi manca qualcosa che sarebbe ovvio per le persone CS, non esitate a segnalarlo.)


Quando dici "designer del linguaggio" intendi una persona che crea il compilatore, o solo la sintassi, o entrambi?
Snoop,

6
@StevieV: Language Design è diverso e indipendente dall'implementazione. Ad esempio, Lisp è stato progettato da John McCarthy come un'alternativa più facile da comprendere al calcolo λ. Tuttavia, non l'ha implementato. In effetti, quando il suo studente Steve Russell voleva implementare Lisp, McCarthy gli disse che credeva che fosse impossibile implementare Lisp! APL è stato progettato come lingua per l'insegnamento della matematica. Successivamente, IBM lo ha utilizzato per specificare formalmente il comportamento di System / 360, per il quale il linguaggio ha ottenuto diverse estensioni. Al momento, non era ancora implementato. Plankalkül è stato progettato da Konrad
Jörg W Mittag il

4
Zuse 1942-1946, ma implementato solo nel 1975. Niklaus Wirth progettò per la prima volta completamente le sue lingue e le implementò solo dopo aver terminato il progetto (e scrisse il primo compilatore nella stessa lingua per avere un'idea di quanto bene fosse la lingua progettato - quindi i suoi studenti hanno tradotto a mano il compilatore in un'altra lingua per il bootstrap). Molte più lingue accademiche non sono mai implementate, sono progettate solo per dimostrare un punto o sperimentare alcune funzionalità linguistiche in modo astratto. Smalltalk è stato creato a seguito di una scommessa reciproca: Alan Kay ha scommesso che poteva
Jörg W Mittag

3
progettando un linguaggio orientato agli oggetti su una singola pagina di carta, Dan Ingalls scommette che potrebbe implementare quel linguaggio in un paio di giorni. (E lo ha fatto in BASIC, di tutte le lingue!) Le lingue sono oggetti matematici che esistono indipendentemente dai loro compilatori / interpreti. E possono essere progettati, studiati e discussi indipendentemente da qualsiasi implementazione fisica.
Jörg W Mittag

3
Deve leggere: Godel, Escher, Bach . A volte è un po 'strano, ma verso la fine entra in gran parte del lavoro di Turing & Godel che influisce notevolmente sulla formalità del design del linguaggio.
RubberDuck,

Risposte:


6

Al momento ho difficoltà a trovare il riferimento esatto, ma qualche tempo fa ho visto diversi video di Simon Peyton Jones , che ha contribuito in modo determinante al design di Haskell. È un eccellente oratore di teoria dei tipi, design del linguaggio e simili, tra l'altro, e ha molti video disponibili gratuitamente su YouTube.

Haskell ha una rappresentazione intermedia che è essenzialmente un calcolo lambda arricchito con alcune semplici cose per semplificare il lavoro. Il calcolo lambda è stato usato e provato poiché un computer era solo una persona che calcolava le cose. Un punto interessante che Simon Peyton Jones sottolinea spesso è che ogni volta che fanno qualcosa di folle e folle con la lingua, sa che è fondamentalmente sano quando alla fine si riduce a quella lingua intermedia.

Altre lingue non sono così rigorose, ma preferiscono la facilità d'uso o l'implementazione. Fanno le stesse cose che fanno altri programmatori per ottenere un codice di alta qualità: seguire le buone pratiche di codifica e testarlo a morte. Una caratteristica come la semantica della proprietà di Rust, sono sicuro che ottiene sia molte analisi formali che test per trovare casi d'angolo dimenticati. Spesso caratteristiche del genere iniziano come tesi di laurea di qualcuno.


2
Credo che il riferimento che stai cercando sia in una delle serie "Avventure con i tipi in Haskell", probabilmente questo dato il contenuto del tabellone nell'immagine in miniatura ...
Jules

8

Quindi, per la progettazione del linguaggio , ci sono prove (o bug). Ad esempio, digitare sistemi. Tipi e linguaggi di programmazione è il libro canonico che descrive i sistemi di tipi e si concentra sulla dimostrazione della correttezza e completezza del sistema di tipi. Le grammatiche hanno un'analisi simile e gli algoritmi (come il sistema di proprietà che descrivi) hanno i loro.

Per l' implementazione del linguaggio , è un codice come un altro. Scrivi unit test. Scrivi test di integrazione. Fai recensioni di codice.

L'unica cosa che rende speciali le lingue è che sono (quasi sempre) infinite. Non puoi letteralmente testare tutti gli input. E (idealmente) vengono utilizzati da tonnellate di persone, facendo cose strane e interessanti, quindi alla fine si troverà qualsiasi bug nella lingua .

In pratica , relativamente poche lingue usano le prove per verificarne la funzionalità e finiscono con una combinazione delle opzioni menzionate.


4
The only thing that makes languages special is that they are (almost always) infinite. You literally cannot test all inputs.È davvero così speciale? Questo sembra essere il caso comune per me. Ad esempio, una funzione che accetta un elenco come argomento ha anche un numero infinito di input. Per ogni taglia n che scegli, c'è un elenco delle dimensioni n + 1.
Doval

@doval - e anche le stringhe, suppongo. Un buon punto
Telastyn,

4

La prima e più difficile cosa che un designer linguistico deve occuparsi quando introduce nuove funzionalità, è mantenere coerente il suo linguaggio:

  • come può essere integrato nella grammatica della lingua senza rompere il codice esistente (questo può essere provato matematicamente)
  • come si relaziona con le funzionalità esistenti (ad esempio se avete array fissi indicizzati 0..n-1, non introdurrete una nuova funzione di array variabile indicizzata 1..n) (questa è la parte artistica del design)
  • come la funzione può essere implementata in tutta la toolchain in modo che la nuova funzionalità possa essere assorbita dall'ecosistema, dai produttori di utensili e dai programmatori (la fattibilità può essere dimostrata con una prova di concetto, ma la piena implementazione è un approccio simile alla programmazione)

Per guidare in questa materia, un designer si basa su una serie di regole e principi di progettazione. Questo approccio è molto ben descritto in " Il design e l'evoluzione del C ++ " di Bjarne Stroustrup , uno dei libri rari dedicati al design del linguaggio. Ciò che è molto interessante è vedere che le lingue sono raramente progettate nel vuoto, e il designer guarda anche come le loro lingue hanno implementato caratteristiche simili. Un'altra fonte (online e gratuita) sono i principi di progettazione per il linguaggio Java .

Se guardi ai procedimenti pubblici dei comitati di standardizzazione, noterai che si tratta più di un processo di errore di prova. Ecco un esempio sul modulo C ++ un concetto completamente nuovo da introdurre nella prossima versione del linguaggio. E qui un'analisi redatta dopo alcuni cambi di lingua , per valutarne il successo. E qui il Java Community Process per definire nuove specifiche Java, come una nuova API . Vedrai che questo lavoro è svolto da diversi esperti che redigono in modo creativo un concept paper e una prima proposta. Quindi queste proposte vengono riviste da una comunità / commissione più ampia che può modificare la proposta per garantire un livello più elevato di coerenza.


4

Come testare le funzioni del linguaggio di programmazione? È un'ottima domanda e non sono sicuro che lo stato dell'arte sia all'altezza.

Ogni nuova funzionalità può interagire con tutte le altre funzionalità. (Ciò influisce su lingua, documenti, compilatori, messaggi di errore, IDE, librerie, ecc.) Le funzionalità si combinano per aprire una scappatoia? Per creare cattivi casi limite?

Anche i progettisti di linguaggi molto intelligenti che lavorano duramente per mantenere la solidità del tipo scoprono violazioni come questo bug Rust . Il sistema dei tipi di Rust non è così ovvio per me, ma penso che in questo caso avere il sistema dei tipi di tracciare il valore dei tempi di vita significhi "sottotipi" di vita (subrange) scontri con le aspettative di sottotipi, coercizioni, riferimenti e mutabilità ordinari, creando una scappatoia in cui una staticvita ref può puntare a un valore allocato in pila e successivamente diventare un riferimento pendente.

Con "funziona correttamente" intendo che la funzione risolve correttamente il problema previsto ed è ragionevolmente a prova di proiettile.

Per le lingue destinate ad essere lingue di produzione , ovvero utilizzate da molti programmatori per creare software di produzione affidabili, "funziona correttamente" deve significare ulteriormente risolvere correttamente il problema previsto per il pubblico previsto.

In altre parole, l' usabilità è importante per la progettazione del linguaggio così come lo è per altri tipi di progettazione. Ciò comporta (1) progettazione per l'usabilità (ad es. Conoscenza del pubblico) e (2) test di usabilità.

Un articolo di esempio su questo argomento è "I programmatori sono persone, troppo , i linguaggi di programmazione e i progettisti API possono imparare molto dal campo della progettazione di fattori umani".

Una domanda SE di esempio su questo argomento è La sintassi di qualsiasi linguaggio di programmazione è stata testata sull'usabilità?

Un esempio di test di usabilità ha considerato l'estensione di una funzione di iterazione di elenchi (non ricordo quale lingua) per prendere più elenchi. La gente si aspettava che iterasse attraverso le liste in parallelo o attraverso il prodotto incrociato? I progettisti del linguaggio sono rimasti sorpresi dai risultati dei test di usabilità.

Lingue come Smalltalk, Python e Dart sono state progettate con un'enfasi sull'usabilità. Chiaramente Haskell non lo era.


Haskell è davvero dannatamente utilizzabile. È difficile da imparare solo perché è un paradigma completamente diverso da Python / C / Java ecc. Ma come lingua è piuttosto facile da usare.
punto
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.