Gli operatori sono più chiari da leggere rispetto a parole chiave o funzioni? [chiuso]


14

È un po 'soggettivo, ma spero di avere una comprensione più chiara di quali fattori rendono chiaro un operatore rispetto a ottuso e difficile. Di recente ho preso in considerazione la progettazione di una lingua e un problema su cui cerco sempre di tornare indietro è quando eseguire un'operazione chiave nella lingua come operatore e quando utilizzare una parola chiave o una funzione.

Haskell è in qualche modo noto per questo, poiché gli operatori personalizzati sono facili da creare e spesso un nuovo tipo di dati verrà impacchettato con diversi operatori per l'uso su di esso. La libreria Parsec, ad esempio, include molti operatori per combinare insieme parser, con gemme simili >.e .> non riesco nemmeno a ricordare cosa significano in questo momento, ma ricordo che sono molto facili con cui lavorare una volta memorizzato cosa in realtà significano. Una chiamata di funzione come quella leftCompose(parser1, parser2)sarebbe stata migliore? Certamente più prolisso, ma per certi versi più chiaro.

I sovraccarichi degli operatori in linguaggi simil-C sono un problema simile, ma si confondono con l'ulteriore problema di sovraccaricare il significato degli operatori familiari come +con nuovi significati insoliti.

In qualsiasi nuova lingua, questo sembrerebbe un problema piuttosto difficile. In F #, ad esempio, il casting utilizza un operatore di tipo-casting derivato matematicamente, anziché la sintassi del cast in stile C # o lo stile VB dettagliato. C #: (int32) xVB: CType(x, int32)F #:x :> int32

In teoria un nuovo linguaggio potrebbe avere operatori per la maggior parte delle funzionalità integrate. Invece di defo deco varper la dichiarazione variabile, perché no ! nameo @ nameo qualcosa di simile. Certamente accorcia la dichiarazione seguita da vincolante: @x := 5invece dideclare x = 5 o let x = 5 La maggior parte del codice richiederà molte definizioni di variabili, quindi perché no?

Quando un operatore è chiaro e utile e quando è oscuro?



1
Vorrei che uno dei 1.000 sviluppatori che mi si fossero mai lamentati della mancanza di sovraccarico da parte degli operatori di Java rispondesse a questa domanda.
smp7d,

1
Avevo pensato a APL quando ho finito di scrivere la domanda, ma in qualche modo questo chiarisce il punto e lo sposta in un territorio un po 'meno soggettivo. Penso che la maggior parte delle persone potrebbe essere d'accordo sul fatto che APL perde qualcosa di facile da usare attraverso il suo numero estremo di operatori, ma la quantità di persone che si lamentano quando una lingua non ha un sovraccarico da parte dell'operatore parla sicuramente a favore di alcune funzioni che ben si adattano agire come operatori. Tra operatori personalizzati proibiti e nient'altro che operatori, è necessario nascondere alcune linee guida per l'implementazione e l'utilizzo pratico.
CodexArcanum,

A mio avviso, la mancanza di sovraccarico da parte dell'operatore è discutibile perché privilegia i tipi nativi rispetto ai tipi definiti dall'utente (si noti che Java lo fa anche in altri modi). Sono molto più ambivalente nei confronti degli operatori personalizzati in stile Haskell, che sembrano un invito aperto ai guai ...
comingstorm

2
@comingstorm: in realtà penso che il modo di Haskell sia migliore. Quando si dispone di un insieme finito di operatori che è possibile sovraccaricare in diversi modi, si è spesso costretti a riutilizzare gli operatori in contesti diversi (ad esempio +per la concatenazione di stringhe o <<per i flussi). Con Haskell, d'altra parte, un operatore è solo una funzione con un nome personalizzato (cioè non sovraccaricato) o parte di una classe di tipo, il che significa che mentre è polimorfico, fa la stessa cosa logica per ogni tipo, e ha anche lo stesso tipo di firma. Lo stesso >>vale >>per ogni tipo e non sarà mai un po 'mutevole.
Tikhon Jelvis,

Risposte:


16

da un punto di vista della progettazione del linguaggio generale non è necessario che vi sia alcuna differenza tra funzioni e operatori. Si potrebbero descrivere le funzioni come operazioni di prefisso con qualsiasi arità (o anche variabile). E le parole chiave possono essere viste solo come funzioni o operatori con nomi riservati (che è utile nella progettazione di una grammatica).

Tutte queste decisioni alla fine si riducono a come vuoi leggere la notazione e che come dici tu sia soggettivo, anche se si possono fare alcune ovvie razionalizzazioni, ad esempio usare i soliti operatori infix per la matematica come tutti li conoscono

Infine Dijkstra ha scritto un'interessante giustificazione delle notazioni matematiche che ha usato, che include una buona discussione sui compromessi delle notazioni infix vs prefisso


4
Mi piacerebbe darti un secondo +1 per il link al documento Dijkstra.
Approgrammatore

Ottimo collegamento, hai dovuto pubblicare un conto PayPal! ;)
Adriano Repetti il

8

Per me, un operatore smette di essere utile quando non puoi più leggere la riga di codice ad alta voce o nella tua testa, e ha senso.

Ad esempio, declare x = 5legge come "declare x equals 5"o let x = 5può essere letto come "let x equal 5", il che è molto comprensibile quando letto ad alta voce, tuttavia la lettura @x := 5viene letta come "at x colon equals 5"(o "at x is defined to be 5"se sei un matematico), il che non ha alcun senso.

Quindi, a mio avviso, utilizzare un operatore se il codice può essere letto ad alta voce e compreso da una persona ragionevolmente intelligente che non ha familiarità con la lingua, ma utilizzare i nomi delle funzioni in caso contrario.


5
Non credo @x := 5sia difficile da capire - lo leggo ancora a voce alta come me set x to be equal to 5.
FrustratedWithFormsDesigner

3
@Rachel, in quel caso, :=ha un uso più ampio nella notazione matematica al di fuori dei linguaggi di programmazione. Inoltre, dichiarazioni come x = x + 1diventano un po 'assurde.
ccoakley,

3
Ironia della sorte, avevo dimenticato che alcune persone non avrebbero riconosciuto :=come incarico. Alcune lingue lo usano davvero. Smalltalk, penso, forse è il più noto. Se l'assegnazione e l'uguaglianza debbano essere operatori separati è una lattina completamente diversa di worm, uno probabilmente già coperto da un'altra domanda.
CodexArcanum,

1
@ccoakley Grazie, non sapevo che :=fosse usato in matematica. L'unica definizione che ho trovato era "è definita come", quindi @x := 5sarebbe stata tradotta in inglese "at x is defined to be 5", il che per me non ha ancora il senso che dovrebbe.
Rachel

1
Certamente Pascal ha usato: = come incarico, per la mancanza di una freccia sinistra in ASCII.
Vatine,

4

Un operatore è chiaro e utile quando è familiare. E ciò probabilmente significa che gli operatori di sovraccarico dovrebbero essere fatti solo quando l'operatore scelto è abbastanza vicino (come nella forma, nella precedenza e nell'associatività) come pratica già consolidata.

Ma scavando un po 'oltre, ci sono due aspetti.

Sintassi (infisso per operatore anziché prefisso per sintassi della chiamata di funzione) e denominazione.

Per la sintassi, c'è un altro caso in cui infix è abbastanza più chiaro del prefisso da giustificare che lo sforzo diventi familiare: quando le chiamate sono concatenate e annidate.

Confronta a * b + c * d + e * fcon +(+(*(a, b), *(c, d)), *(e, f))o + + * a b * c d * e f(entrambi sono analizzabili nelle stesse condizioni della versione infix). Il fatto che il+ termini separati invece di precedere uno di essi lo rendono molto più leggibile ai miei occhi (ma bisogna ricordare le regole di precedenza che non sono necessarie per la sintassi del prefisso). Se hai bisogno di combinare le cose in questo modo, il beneficio a lungo termine vale il costo di apprendimento. E mantieni questo vantaggio anche se dai nomi ai tuoi operatori con lettere invece di usare simboli.

Per quanto riguarda la denominazione degli operatori, vedo pochi vantaggi nell'usare qualcos'altro rispetto ai simboli stabiliti o al nome chiaro. Sì, potrebbero essere più brevi, ma sono davvero criptici e saranno rapidamente dimenticati se non hai una buona ragione per conoscerli già.

Un terzo aspetto se si prende un POV di progettazione linguistica, è se l'insieme di operatori deve essere aperto o chiuso - è possibile aggiungere un operatore o no - e se la priorità e l'associatività devono essere specificabili dall'utente o meno. Il mio primo approccio sarebbe quello di stare attenti e non fornire priorità e associatività specificabili dall'utente, mentre sarei più aperto ad avere un set aperto (aumenterebbe la spinta ad usare il sovraccarico dell'operatore, ma diminuirebbe quello di usare un cattivo solo per beneficiare della sintassi infix dove è utile).


Se gli utenti possono aggiungere operatori, perché non consentire loro di impostare la precedenza e l'associatività degli operatori che hanno aggiunto?
Tikhon Jelvis,

@TikhonJelvis, Per lo più è stato prudente, ma ci sono alcuni aspetti da considerare se si desidera essere in grado di usarlo per altro che esempi. Ad esempio, si desidera associare la priorità e l'associatività all'operatore, non a un determinato membro dell'insieme sovraccarico (è stato scelto il membro dopo aver eseguito l'analisi, la scelta non può influenzare l'analisi). Pertanto, per integrare le biblioteche utilizzando lo stesso operatore, devono concordare la sua priorità e associatività. Prima di aggiungere la possibilità, proverei a scoprire perché così poche lingue hanno seguito Algol 68 (nessuna AFAIK) e mi hanno permesso di definirle.
AProgrammer

Bene, Haskell ti consente di definire operatori arbitrari e impostare la loro precedenza e associatività. La differenza è che non è possibile sovraccaricare gli operatori di per sé : si comportano come normali funzioni. Ciò significa che non è possibile avere librerie diverse che provano a utilizzare lo stesso operatore per cose diverse. Dato che puoi definire il tuo operatore, ci sono molte meno ragioni per riutilizzare gli stessi per cose diverse.
Tikhon Jelvis,

1

Gli operatori come simboli sono utili quando hanno un senso intuitivo. +e -sono ovviamente addizioni e sottrazioni, per esempio, motivo per cui praticamente tutte le lingue le usano mai come loro operatori. Lo stesso vale per il confronto ( <e >viene insegnato nelle elementari <=e >=sono estensioni intuitive dei confronti di base quando si comprende che non ci sono tasti di confronto sottolineati sulla tastiera standard.)

*e /sono immediatamente meno ovvi, ma sono usati universalmente e la loro posizione sul tastierino numerico proprio accanto ai tasti +e -aiuta a fornire un contesto. C <<e >>sono nella stessa categoria: quando capisci cosa sono uno spostamento a sinistra e uno a destra, non è troppo difficile capire la mnemonica dietro le frecce.

Poi vieni ad alcuni di quelli davvero strani, cose che possono trasformare il codice C in minestra di operatore illeggibile. (Selezionando C qui perché è un esempio molto pesante per l'operatore la cui sintassi è praticamente nota a tutti, attraverso il lavoro con C o con le lingue discendenti.) Ad esempio, quindi %operatore. Tutti sanno cos'è: è un segno di percentuale ... giusto? Tranne in C, non ha nulla a che fare con la divisione per 100; è l'operatore del modulo. Ciò ha senso zero in modo mnemonico, ma è così!

Ancora peggio sono gli operatori booleani. &come e ha un senso, tranne che ci sono due &operatori diversi che fanno due cose diverse, ed entrambi sono sintatticamente validi in ogni caso in cui uno di essi è valido, anche se solo uno di loro ha effettivamente senso in un dato caso. Questa è solo una ricetta per la confusione! Gli operatori or , xor e not sono anche peggio, dal momento che non usano simboli mnemonicamente utili e non sono coerenti. (Nessun operatore double-xor , e il logico e bit a bit non usano due simboli diversi invece di un simbolo e una versione raddoppiata.)

E solo per peggiorare le cose, gli operatori &e *vengono riutilizzati per cose completamente diverse, il che rende il linguaggio più difficile da analizzare sia per le persone che per i compilatori. (Cosa A * Bsignifica? Dipende interamente dal contesto: è Aun tipo o una variabile?)

Certo, non ho mai parlato con Dennis Ritchie e non gli ho chiesto delle decisioni di progettazione che ha preso nella lingua, ma così tanto sembra che la filosofia alla base degli operatori fosse "simboli per amore dei simboli".

In contrasto con Pascal, che ha gli stessi operatori di C, ma una filosofia diversa nel rappresentarli: gli operatori dovrebbero essere intuitivi e di facile lettura. Gli operatori aritmetici sono gli stessi. L'operatore del modulo è la parola mod, perché non c'è alcun simbolo sulla tastiera che ovviamente significhi "modulo". Gli operatori logici sono and, or, xore not, e c'è solo uno di ciascuno; il compilatore sa se hai bisogno della versione booleana o bit a bit in base al fatto che tu stia operando su valori booleani o numeri, eliminando un'intera classe di errori. Ciò rende il codice Pascal molto più semplice da comprendere rispetto al codice C, specialmente in un editor moderno con evidenziazione della sintassi in modo che gli operatori e le parole chiave siano visivamente distinti dagli identificatori.


4
Pascal non rappresenta affatto una filosofia diversa. Se leggi gli articoli di Wirth, usa simboli per cose del genere ande per ortutto il tempo. Quando ha sviluppato Pascal, tuttavia, lo ha fatto su un mainframe Control Data, che utilizzava caratteri a 6 bit. Ciò ha costretto la decisione di usare le parole per molte cose, per la semplice ragione che il set di caratteri semplicemente non aveva lo stesso spazio per i simboli e qualcosa come ASCII. Ho usato sia C che Pascal e trovo C sostanzialmente più leggibile. Puoi preferire Pascal, ma è una questione di gusti, non di fatto.
Jerry Coffin,

Per aggiungere all'osservazione di @JerryCoffin su Wirth, le sue lingue successive (Modula, Oberon) stanno usando più simboli.
Approgrammatore

@AProgrammer: E non sono mai andati da nessuna parte. ;)
Mason Wheeler,

Penso |(e, per estensione, ||) per o ha senso. Lo vedi anche continuamente per l'alternanza in altri contesti, come le grammatiche, e sembra che stia dividendo due opzioni.
Tikhon Jelvis,

1
Un vantaggio delle parole chiave basate su lettere è che lo spazio è molto più ampio dei simboli. Ad esempio, un linguaggio in stile Pascal può includere operatori sia per "modulo" che per "resto", e per divisione intera rispetto a virgola mobile (che hanno significati diversi oltre i tipi dei loro operandi).
supercat,

1

Penso che gli operatori siano molto più leggibili quando la loro funzione rispecchia da vicino il loro scopo familiare e matematico. Ad esempio, gli operatori standard come +, -, ecc. Sono più leggibili di Aggiungi o Sottrai durante l'esecuzione di addizioni e sottrazioni. Il comportamento dell'operatore deve essere chiaramente definito. Posso tollerare un sovraccarico del significato di + per la concatenazione di elenchi, ad esempio. Idealmente l'operazione non avrebbe effetti collaterali, restituendo un valore invece di mutare.

Ho lottato con operatori di scorciatoie per funzioni come fold, però. Ovviamente più esperienza con loro faciliterebbe questo, ma trovo foldRightpiù leggibile di /:.


4
Forse vi arriva anche la frequenza d'uso. Non penserei che foldabbastanza spesso un operatore risparmierebbe un sacco di tempo. Questo potrebbe anche essere il motivo per cui LISPy (+ 1 2 3) sembra strano ma (aggiungi 1 2 3) lo è di meno. Tendiamo a pensare agli operatori come strettamente binari. Gli >>.operatori di stile di Parsec possono essere sciocchi, ma almeno la cosa principale che fai in Parsec è combinare parser, quindi gli operatori per questo ottengono molto uso.
CodexArcanum,

1

Il problema con gli operatori è che ne esiste solo un piccolo numero rispetto al numero di nomi di metodi sensuali (!) Che è possibile utilizzare invece. Un effetto collaterale è che gli operatori definibili dall'utente tendono a introdurre molti sovraccarichi.

Certamente, la riga C = A * x + b * cè più facile da scrivere e da leggere rispetto a C = A.multiplyVector(x).addVector(b.multiplyScalar(c)).

O, beh, è ​​sicuramente più facile da scrivere, se hai tutte le versioni sovraccariche di tutti quegli operatori nella tua testa. E puoi leggerlo in seguito, risolvendo il penultimo bug.

Ora, per la maggior parte del codice che si sta esaurendo, va bene. "Il progetto supera tutti i test e funziona" - per molti software tutto ciò che potremmo desiderare.

Ma le cose vanno diversamente quando si esegue un software che va in aree critiche. Roba fondamentale per la sicurezza. Sicurezza. Software che mantiene gli aerei in volo o che mantiene in funzione le strutture nucleari. O software che crittografa le tue e-mail altamente confidenziali.

Per gli esperti di sicurezza specializzati nel codice di controllo, questo può essere un incubo. Il mix di numerosi operatori definiti dall'utente e il sovraccarico degli operatori può lasciarli nella spiacevole situazione in cui hanno difficoltà a capire quale codice verrà eseguito alla fine.

Quindi, come affermato nella domanda originale, questo è un argomento altamente soggettivo. E mentre molti suggerimenti su come gli operatori dovrebbero essere usati potrebbero sembrare perfettamente sensati, la combinazione di pochi di essi potrebbe creare molti problemi a lungo termine.


0

Suppongo che l'esempio più estremo sia APL, il seguente programma è il "gioco della vita":

life←{↑1 ⍵∨.∧3 4=+/,¯1 0 1∘.⊖¯1 0 1∘.⌽⊂⍵}

E se riesci a capire cosa significa tutto senza passare un paio di giorni con il manuale di riferimento, allora buona fortuna a te!

Il problema è che hai una serie di simboli che quasi tutti comprendono intuitivamente: +,-,*,/,%,=,==,&

e il resto che richiede una spiegazione prima di poter essere compreso, e, sono generalmente specifici per la lingua particolare. Ad esempio, non esiste un simbolo ovvio per specificare quale membro di un array desideri, [], () e persino "." sono stati usati, ma allo stesso modo non esiste una parola chiave ovvia che potresti facilmente usare, quindi c'è un caso da fare per un uso giudizioso degli operatori. Ma non troppi.

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.