Qual è il vantaggio di avere un operatore di assegnazione che restituisce un valore?

26

Sto sviluppando un linguaggio che intendo sostituire sia Javascript che PHP. (Non riesco a vedere alcun problema con questo. Non è che nessuna di queste lingue abbia una base di installazione estesa.)

Una delle cose che volevo cambiare era trasformare l'operatore di assegnazione in un comando di assegnazione, rimuovendo la possibilità di utilizzare il valore restituito.

x=1;          /* Assignment. */
if (x==1) {}  /* Comparison. */
x==1;         /* Error or warning, I've not decided yet. */
if (x=1) {}   /* Error. */

So che questo significherebbe che quelle funzioni di una sola riga che la gente C ama così tanto non funzionerebbero più. Ho pensato (con poche prove al di là della mia esperienza personale) che la stragrande maggioranza delle volte questo è accaduto, in realtà era destinato ad essere un'operazione di confronto.

O è? Ci sono degli usi pratici del valore di ritorno dell'operatore di assegnazione che non è stato possibile riscrivere in modo banale? (Per qualsiasi lingua che abbia un tale concetto.)

    
posta billpg 13.02.2014 - 13:10
fonte

9 risposte

24

Tecnicamente, uno zucchero sintattico può valere la pena di essere mantenuto anche se può essere banalmente sostituito, se migliora la leggibilità di qualche operazione comune. Ma l'assegnazione-come-espressione non rientra in questo. Il pericolo di digitarlo al posto di un confronto significa che è usato raramente (a volte anche proibito dalle guide di stile) e provoca una doppia presa ogni volta che viene utilizzato. In altre parole, i vantaggi di leggibilità sono piccoli in numero e grandezza.

Potrebbe essere utile dare un'occhiata alle lingue esistenti.

  • Java e C # mantengono l'incarico un'espressione, ma rimuovono la trappola che menzioni richiedendo condizioni per valutare i booleani. Questo per lo più sembra funzionare bene, anche se la gente occasionalmente si lamenta che ciò non consente condizioni come if (x) al posto di if (x != null) o if (x != 0) a seconda del tipo di x .
  • Python rende l'assegnazione un'istruzione corretta anziché un'espressione. Le proposte per cambiarlo occasionalmente raggiungono la mailing list di python-ideas, ma la mia impressione soggettiva è che ciò accade più raramente e genera meno rumore ogni volta rispetto ad altre funzionalità "mancanti" come loop di do-while, istruzioni switch, lambda multi-line, ecc.

Tuttavia, Python consente un caso speciale, assegnando a più nomi contemporaneamente: a = b = c . Questa è considerata una dichiarazione equivalente a b = c; a = b , ed è usata occasionalmente, quindi potrebbe valere la pena aggiungere alla tua lingua (ma non la suderei, dato che questa aggiunta dovrebbe essere retrocompatibile).

    
risposta data 13.02.2014 - 13:36
fonte
11

Are there any practical uses of the assignment operator's return value that could not be trivially rewritten?

In generale, no. L'idea di avere il valore di un'espressione di assegnazione è il valore che è stato assegnato significa che abbiamo un'espressione che può essere usata sia per il suo effetto collaterale che per il suo valore , e che molti considerano confuso.

Gli usi comuni sono in genere per rendere le espressioni compatte:

x = y = z;

ha la semantica in C # di "convertire z nel tipo di y, assegnare il valore convertito a y, il valore convertito è il valore dell'espressione, convertirlo nel tipo di x, assegnare a x".

Ma siamo già nel campo degli effetti collaterali impertinativi in un contesto di dichiarazione, quindi c'è davvero molto poco avvincente vantaggio su questo oltre

y = z;
x = y;

Analogamente a

M(x = 123);

essere una scorciatoia per

x = 123;
M(x);

Ancora una volta, nel codice originale stiamo usando un'espressione sia per i suoi effetti collaterali che per il suo valore, e stiamo facendo una dichiarazione che ha due effetti collaterali invece di uno. Entrambi sono puzzolenti; prova ad avere un effetto collaterale per affermazione e usa le espressioni per i loro valori, non per i loro effetti collaterali.

I'm developing a language which I intend to replace both Javascript and PHP.

Se vuoi davvero essere audace e sottolineare che l'assegnazione è una dichiarazione e non un'uguaglianza, allora il mio consiglio è: rendilo chiaramente una dichiarazione di assegnazione .

let x be 1;

Ecco fatto. O

x <-- 1;

o anche meglio:

1 --> x;

O ancora meglio ancora

1 → x;

Non è assolutamente possibile che qualcuno di questi venga confuso con x == 1 .

    
risposta data 13.02.2014 - 19:00
fonte
9

Molte lingue scelgono il percorso di assegnazione di un compito piuttosto che un'espressione, incluso Python:

foo = 42 # works
if foo = 42: print "hi" # dies
bar(foo = 42) # keyword arg

e Golang:

var foo int
foo = 42 # works
if foo = 42 { fmt.Printn("hi") } # dies

Altre lingue non hanno assegnazioni, ma piuttosto associazioni con scope, ad es. OCaml:

let foo = 42 in
  if foo = 42 then
    print_string "hi"

Tuttavia, let è un'espressione stessa.

Il vantaggio di consentire l'assegnazione è che possiamo controllare direttamente il valore di ritorno di una funzione all'interno del condizionale, ad es. in questo frammento di Perl:

if (my $result = some_computation()) {
  say "We succeeded, and the result is $result";
}
else {
  warn "Failed with $result";
}

Perl estende ulteriormente la dichiarazione solo a quella condizionale, cosa che lo rende molto utile. Ti avvertirà anche se assegni all'interno di un condizionale senza dichiarare una nuova variabile - if ($foo = $bar) avviserà, if (my $foo = $bar) no.

Rendere il compito in un'altra istruzione è di solito sufficiente, ma può portare problemi di scoping:

my $result = some_computation()
if ($result) {
  say "We succeeded, and the result is $result";
}
else {
  warn "Failed with $result";
}
# $result is still visible here - eek!

Golang fa molto affidamento sui valori di ritorno per il controllo degli errori. Pertanto consente a un condizionale di prendere una dichiarazione di inizializzazione:

if result, err := some_computation(); err != nil {
  fmt.Printf("Failed with %d", result)
}
fmt.Printf("We succeeded, and the result is %d\n", result)

Altre lingue usano un sistema di tipi per non consentire espressioni non booleane all'interno di un condizionale:

int foo;
if (foo = bar()) // Java does not like this

Certo che fallisce quando si utilizza una funzione che restituisce un valore booleano.

Ora abbiamo visto diversi meccanismi per difendersi dall'assegnazione accidentale:

  • Non consentire il compito come espressione
  • Usa controllo tipo statico
  • L'assegnazione non esiste, abbiamo solo let binding
  • Consenti una dichiarazione di inizializzazione, non consentire altrimenti l'assegnazione
  • Non consentire il compito all'interno di un condizionale senza dichiarazione

Li ho classificati in ordine di preferenza ascendente - gli assegnamenti all'interno delle espressioni possono essere utili (ed è semplice aggirare i problemi di Python avendo una sintassi esplicativa delle dichiarazioni e una sintassi differente degli argomenti con nome). Ma è giusto disabilitarli, poiché ci sono molte altre opzioni per lo stesso effetto.

Il codice senza bug è più importante del codice tergente.

    
risposta data 13.02.2014 - 13:48
fonte
7

Hai detto "Ho capito (con poche prove al di là della mia esperienza personale) che la stragrande maggioranza delle volte questo è accaduto, in realtà era inteso per essere un'operazione di confronto."

Perché non FISSARE IL PROBLEMA?

Invece di = per l'assegnazione e == per il test di uguaglianza, perché non usare: = per l'assegnazione e = (o anche ==) per l'uguaglianza?

Osservate:

if (a=foo(bar)) {}  // obviously equality
if (a := foo(bar)) { do something with a } // obviously assignment

Se vuoi rendere più difficile per il programmatore scambiare l'assegnazione per l'uguaglianza, rendi più difficile.

Allo stesso tempo, se davvero volessi risolvere il problema, dovresti rimuovere il c crock che affermava che i booleani erano solo interi con nomi simbolici predefiniti di zucchero. Rendili del tutto diversi. Quindi, invece di dire

int a = some_value();
if (a) {}

costringi il programmatore a scrivere:

int a = some_value();
if (a /= 0) {} // Note that /= means 'not equal'.  This is your Ada lesson for today.

Il fatto è che l'assegnazione come operatore è un costrutto molto utile. Non abbiamo eliminato le lamette da barba perché alcune persone si sono tagliate. Invece, il re Gillette ha inventato il rasoio di sicurezza.

    
risposta data 13.02.2014 - 14:20
fonte
4

Per rispondere effettivamente alla domanda, sì, ci sono numerosi usi di questo anche se sono leggermente di nicchia.

Ad esempio in Java:

while ((Object ob = x.next()) != null) {
    // This will loop through calling next() until it returns null
    // The value of the returned object is available as ob within the loop
}

L'alternativa senza utilizzare l'assegnazione incorporata richiede che ob sia definita al di fuori dell'ambito del ciclo e due posizioni di codice separate che chiamano x.next ().

È già stato detto che puoi assegnare più variabili in un solo passaggio.

x = y = z = 3;

Questo genere di cose è l'uso più comune, ma i programmatori creativi ne troveranno sempre di più.

    
risposta data 14.02.2014 - 10:19
fonte
1

Dal momento che ottieni tutte le regole, perché ora permetti al compito di trasformare un valore, e semplicemente non permetti assegnazioni all'interno di passaggi condizionali? Questo ti dà lo zucchero sintattico per facilitare le inizializzazioni, evitando comunque un errore di codifica comune.

In altre parole, rendi questo legale:

a=b=c=0;

Ma rendilo illegale:

if (a=b) ...
    
risposta data 13.02.2014 - 13:56
fonte
0

Dai suoni, sei sulla strada della creazione di un linguaggio abbastanza severo.

Tenendo questo a mente, costringi le persone a scrivere:

a=c;
b=c;

invece di:

a=b=c;

potrebbe sembrare un miglioramento per impedire alle persone di fare:

if (a=b) {

quando intendevano fare:

if (a==b) {

ma alla fine, questo tipo di errori è facile da rilevare e avvisare se sono o meno codici legali.

Tuttavia, ci sono situazioni in cui si fa:

a=c;
b=c;

non significa che

if (a==b) {

sarà vero.

Se c è in realtà una funzione c (), potrebbe restituire risultati diversi ogni volta che viene chiamato. (potrebbe anche essere troppo costoso dal punto di vista computazionale ...)

Allo stesso modo se c è un puntatore all'hardware mappato in memoria, quindi

a=*c;
b=*c;

sono entrambi probabilmente diversi, e possono anche avere effetti elettronici sull'hardware su ogni lettura.

Ci sono molte altre permutazioni con l'hardware in cui è necessario essere precisi su quali indirizzi di memoria vengono letti, scritti e in specifici vincoli temporali, dove fare più assegnazioni sulla stessa linea è veloce, semplice ed evidente, senza il il timing rischia di introdurre variabili temporanee

    
risposta data 13.02.2014 - 13:51
fonte
0

Il più grande vantaggio nella mia mente di avere l'incarico di essere un'espressione è che permette alla tua grammatica di essere più semplice se uno dei tuoi obiettivi è che "tutto è un'espressione" - un obiettivo di LISP in particolare.

Python non ha questo; ha espressioni e dichiarazioni, l'incarico è una dichiarazione. Ma poiché Python definisce una forma lambda come una singola espressione parametrizzata , ciò significa che non puoi assegnare variabili all'interno di una lambda. Questo a volte è inopportuno, ma non un problema critico, ed è l'unico lato negativo della mia esperienza nell'assegnare un compito a una dichiarazione in Python.

Un modo per consentire al compito, o meglio all'effetto del compito, di essere un'espressione senza introdurre il potenziale per if(x=1) di incidenti che C deve utilizzare un costrutto% L_S_come let , come (let ((x 2) (y 3)) (+ x y)) che potrebbe nella tua lingua valutare come 5 . L'utilizzo di let in questo modo non deve essere tecnicamente assegnato alla tua lingua, se definisci let come creando un ambito lessicale. Definito in questo modo, un costrutto let potrebbe essere compilato allo stesso modo della costruzione e della chiamata di una funzione di chiusura annidata con argomenti.

D'altra parte, se sei semplicemente preoccupato del caso if(x=1) , ma vuoi che l'assegnazione sia un'espressione come in C, sarà sufficiente scegliere semplicemente token diversi. Assegnazione: x := 1 o x <- 1 . Confronto: x == 1 . Errore di sintassi: x = 1 .

    
risposta data 13.02.2014 - 22:15
fonte
0

I know that this would mean that those one-line functions that C people love so much would no longer work. I figured (with little evidence beyond my personal experience) that the vast majority of times this happened, it was really intended to be comparison operation.

In effetti. Questo non è niente di nuovo, tutti i sottoinsiemi sicuri del linguaggio C hanno già fatto questa conclusione.

MISRA-C, CERT-C e così via per tutte le assegnazioni di divieto all'interno delle condizioni, semplicemente perché è pericoloso.

Non esiste alcun caso in cui il codice che si basa sull'assegnazione all'interno delle condizioni non possa essere riscritto.

Inoltre, tali standard mettono in guardia anche contro la scrittura di codice che si basa sull'ordine di valutazione. Più assegnazioni su una singola riga x=y=z; è un caso simile. Se una riga con più assegnazioni contiene effetti collaterali (funzioni di chiamata, accesso a variabili volatili, ecc.), Non è possibile sapere quale effetto collaterale si verificherà per primo.

Non ci sono punti di sequenza tra la valutazione degli operandi. Quindi non possiamo sapere se la sottoespressione y viene valutata prima o dopo z : si tratta di un comportamento non specificato in C. Quindi tale codice è potenzialmente inaffidabile, non portatile e non conforme ai sottosistemi sicuri citati di C.

La soluzione sarebbe stata sostituire il codice con y=z; x=y; . Questo aggiunge un punto di sequenza e garantisce l'ordine di valutazione.

Quindi, in base a tutti i problemi che ciò ha causato in C, qualsiasi linguaggio moderno farebbe bene a vietare l'assegnazione all'interno delle condizioni, nonché a più assegnazioni su una singola riga.

    
risposta data 14.02.2014 - 11:49
fonte

Leggi altre domande sui tag