Perché gli operatori definiti dall'utente non sono più comuni?


94

Una caratteristica che mi manca nei linguaggi funzionali è l'idea che gli operatori siano solo funzioni, quindi aggiungere un operatore personalizzato è spesso semplice come aggiungere una funzione. Molti linguaggi procedurali consentono sovraccarichi degli operatori, quindi in un certo senso gli operatori sono ancora funzioni (questo è molto vero in D dove l'operatore viene passato come stringa in un parametro modello).

Sembra che laddove sia consentito il sovraccarico degli operatori, spesso sia banale aggiungere altri operatori personalizzati. Ho trovato questo post sul blog , in cui si afferma che gli operatori personalizzati non funzionano bene con la notazione infissa a causa delle regole di precedenza, ma l'autore offre diverse soluzioni a questo problema.

Mi sono guardato intorno e non sono riuscito a trovare linguaggi procedurali che supportino operatori personalizzati nella lingua. Ci sono hack (come le macro in C ++), ma non è lo stesso del supporto del linguaggio.

Poiché questa funzionalità è piuttosto banale da implementare, perché non è più comune?

Capisco che può portare a qualche brutto codice, ma che in passato non ha impedito ai progettisti di linguaggi di aggiungere funzioni utili che possono essere facilmente abusate (macro, operatore ternario, puntatori non sicuri).

Casi d'uso reali:

  • Implementa operatori mancanti (ad es. Lua non ha operatori bit a bit)
  • Mimic D's ~(concatenazione di array)
  • DSL
  • Utilizzare |come zucchero di sintassi tipo pipe Unix (usando coroutine / generatori)

Sono anche interessato a lingue che fanno consentire agli operatori personalizzati, ma io sono più interessato a perché essa è stata esclusa. Ho pensato di creare un linguaggio di scripting per aggiungere operatori definiti dall'utente, ma mi sono fermato quando mi sono reso conto di non averlo visto da nessuna parte, quindi probabilmente c'è una buona ragione per cui i progettisti di lingue più intelligenti di me non l'hanno permesso.


4
C'è una discussione su Reddit in corso su questa domanda.
Dinamico

2
@dimatura: non so molto su R, ma i suoi operatori personalizzati non sembrano ancora molto flessibili (ad es. nessun modo corretto di definire fissità, portata, sovraccarico ecc.), il che spiega perché non sono usati molto. Questo è diverso in altre lingue, certamente in Haskell che utilizza operatori infissi su misura un grande sacco . Un altro esempio di un linguaggio procedurale con un supporto di infusione ragionevolmente personalizzato è Nimrod , e ovviamente anche Perl lo consente .
leftaroundabout

4
C'è l'altra opzione, Lisp in realtà non ha differenze tra operatori e funzioni.
Bearded:

4
Nessuna menzione di Prolog, un'altra lingua in cui gli operatori sono solo zucchero sintattico per le funzioni (sì, anche quelle matematiche) e che consente di definire operatori personalizzati con precedenza personalizzata.
David Cowden,

2
@BeardedO, non ci sono operatori infix in Lisp. Dopo averli presentati, dovrai affrontare tutti i problemi con precedenza e simili.
SK-logic

Risposte:


134

Esistono due scuole di pensiero diametralmente opposte nella progettazione del linguaggio di programmazione. Uno è che i programmatori scrivono un codice migliore con meno restrizioni e l'altro è che scrivono un codice migliore con più restrizioni. A mio avviso, la realtà è che i programmatori di buona esperienza prosperano con meno restrizioni, ma tali restrizioni possono avvantaggiare la qualità del codice dei principianti.

Gli operatori definiti dall'utente possono creare codice molto elegante in mani esperte e un codice assolutamente terribile da parte di un principiante. Quindi, se la tua lingua li include o meno dipende dalla scuola di pensiero del tuo progettista di lingue.


23
Sulle due scuole di pensiero, plus.google.com/110981030061712822816/posts/KaSKeg4vQtz anche +1 per avere la risposta più diretta (e probabilmente la più vera) alla domanda sul perché i designer linguistici scelgono l'uno rispetto all'altro. Direi anche che in base alla tua tesi potresti estrapolare che un minor numero di lingue lo consente perché una porzione più piccola di sviluppatori è altamente qualificata rispetto al resto (la percentuale più alta di qualsiasi cosa sarà sempre una minoranza per definizione)
Jimmy Hoffa,

12
Penso che questa risposta sia vaga e solo marginalmente correlata alla domanda. (Mi capita anche di pensare che la tua caratterizzazione sia sbagliata poiché contraddice la mia esperienza ma non è importante.)
Konrad Rudolph,

1
Come ha detto @KonradRudolph, questo non risponde davvero alla domanda. Prendi una lingua come Prolog che ti consente di fare qualsiasi cosa tu voglia con gli operatori, compresa la definizione della loro precedenza quando vengono inseriti infissi o prefissi. Non penso che il fatto che tu possa scrivere operatori personalizzati abbia qualcosa a che fare con il livello di abilità del pubblico target, ma piuttosto perché l'obiettivo di Prolog è leggere il più logicamente possibile. L'inclusione di operatori personalizzati consente di scrivere programmi che leggono in modo molto logico (dopo tutto un programma prolog è solo un mucchio di istruzioni logiche).
David Cowden,

Come ho perso Stevey Rant? Oh amico, ho
aggiunto un

In quale filosofia dovrei cadere se penso che i programmatori hanno più probabilità di scrivere il codice migliore se le lingue danno loro gli strumenti per dire quando il compilatore dovrebbe fare varie inferenze (ad esempio argomenti di auto-boxing su un Formatmetodo) e quando dovrebbe rifiutare ( es. argomenti di auto-boxing a ReferenceEquals). Più il linguaggio delle abilità offre ai programmatori di dire quando certe inferenze sarebbero inadeguate, più in modo sicuro può offrire inferenze convenienti quando appropriato.
supercat

83

Con una scelta tra array concatenati con ~ o con "myArray.Concat (secondArray)", probabilmente preferirei quest'ultimo. Perché? Perché ~ è un personaggio completamente insignificante che ha solo il suo significato - quello della concatenazione di array - dato nello specifico progetto in cui è stato scritto.

Fondamentalmente, come hai detto, gli operatori non sono diversi dai metodi. Ma mentre ai metodi possono essere dati nomi leggibili e comprensibili che si aggiungono alla comprensione del flusso di codice, gli operatori sono opachi e situazionali.

Questo è il motivo per cui non mi piace nemmeno l' .operatore di PHP (concatenazione di stringhe) o la maggior parte degli operatori in Haskell o OCaml, anche se in questo caso stanno emergendo alcuni standard universalmente accettati per i linguaggi funzionali.


27
Lo stesso si può dire di tutti gli operatori. Ciò che li rende buoni e che può anche rendere buoni gli operatori definiti dall'utente (se definiti in modo giudizioso) è questo: nonostante il carattere utilizzato per loro sia solo un carattere, un uso diffuso e possibilmente proprietà mnemoniche ne fanno immediatamente un significato nel codice sorgente ovvio per chi lo conosce. Quindi la tua risposta non è così convincente IMHO.

33
Come ho detto alla fine, alcuni operatori hanno una riconoscibilità universale (come i simboli aritmentici standard, i cambiamenti bit a bit, AND / OR e così via), e quindi la loro terseness supera la loro opacità. Ma essere in grado di definire operatori arbitrari sembra mantenere il peggio di entrambi i mondi.
Avner Shahar-Kashtan,

11
Quindi sei anche favorevole a vietare, ad esempio, nomi di una sola lettera a livello di lingua? Più in generale, non consentire nulla nella lingua che sembra fare più male che bene? Questo è un approccio valido al design del linguaggio, ma non l'unico, quindi non credo che tu non possa supporlo.

9
Non sono d'accordo sul fatto che gli operatori personalizzati stiano "aggiungendo" una funzionalità, rimuovendo una limitazione. Gli operatori sono generalmente solo funzioni, quindi al posto di una tabella di simboli statici per gli operatori, è possibile utilizzare invece una tabella dinamica e dipendente dal contesto. Immagino che sia così che vengono gestiti i sovraccarichi dell'operatore, perché +e <<certamente non sono definiti su Object(ottengo "nessuna corrispondenza per l'operatore + in ..." quando lo faccio su una classe nuda in C ++).
beatgammit,

10
Ciò che richiede molto tempo per capire è che la codifica di solito non consiste nel far sì che un computer faccia qualcosa per te, si tratta di generare un documento che sia le persone che i computer possono leggere, con le persone un po 'più importanti dei computer. Questa risposta è esattamente corretta, se la persona successiva (o tu tra 2 anni) deve impiegare anche 10 secondi a cercare di capire cosa significhi ~, potresti anche aver trascorso i 10 secondi digitando una chiamata di metodo.
Bill K,

71

Poiché questa funzionalità è piuttosto banale da implementare, perché non è più comune?

La tua premessa è sbagliata. E ' non è “abbastanza banale da attuare”. In realtà, porta un sacco di problemi.

Diamo un'occhiata alle "soluzioni" suggerite nel post:

  • No la precedenza . L'autore stesso dice "Non usare le regole di precedenza semplicemente non è un'opzione".
  • Analisi semantica consapevole . Come dice l'articolo, questo richiederebbe al compilatore di avere molte conoscenze semantiche. L'articolo in realtà non offre una soluzione per questo e lascia che te lo dica, questo semplicemente non è banale. I compilatori sono progettati come un compromesso tra potenza e complessità. In particolare, l'autore menziona una fase di pre-analisi per raccogliere le informazioni pertinenti, ma la pre-analisi è inefficiente e i compilatori si impegnano molto per ridurre al minimo i passaggi di analisi.
  • Nessun operatore infix personalizzato . Bene, questa non è una soluzione.
  • Soluzione ibrida . Questa soluzione comporta molti (ma non tutti) gli svantaggi dell'analisi consapevole semantica. In particolare, poiché il compilatore deve trattare i token sconosciuti come potenzialmente rappresentanti operatori personalizzati, spesso non può produrre messaggi di errore significativi. Potrebbe anche essere necessario che la definizione di detto operatore proceda all'analisi (per raccogliere informazioni sul tipo, ecc.), Richiedendo ancora una volta un ulteriore passaggio di analisi.

Tutto sommato, questa è una funzione costosa da implementare, sia in termini di complessità del parser che in termini di prestazioni, e non è chiaro che porterebbe molti vantaggi. Certo, ci sono alcuni vantaggi nella capacità di definire nuovi operatori ma anche quelli sono controversi (basta guardare le altre risposte sostenendo che avere nuovi operatori non è una buona cosa).


2
Grazie per la voce di sanità mentale. La mia esperienza suggerisce che le persone che giudicano banali le cose siano o esperti o beati ignoranti, e più spesso in quest'ultima categoria: x
Matthieu M.

14
ognuno di questi problemi è stato risolto in lingue che esistono da 20 anni ...
Philip JF

11
Eppure, è eccezionalmente banale da implementare. Dimentica tutti gli algoritmi di analisi del libro dei draghi, è un 21 ° secolo ed è tempo di andare avanti. Anche estendere la stessa sintassi del linguaggio è semplice e aggiungere operatori di una data precedenza è banale. Dai un'occhiata, diciamo, al parser Haskell. È molto, molto più semplice dei parser per le lingue gonfiate "tradizionali".
SK-logic,

3
@ SK-logic La tua affermazione generale non mi convince. Sì, l'analisi è andata avanti. No, l'implementazione di precedenza di operatori arbitrari a seconda del tipo non è "banale". Produrre buoni messaggi di errore con un contesto limitato non è "banale". Non è possibile produrre parser efficienti con lingue che richiedono passaggi multipli per trasformarsi.
Konrad Rudolph,

6
In Haskell, l'analisi è "facile" (rispetto alla maggior parte delle lingue). La precedenza è indipendente dal contesto. La parte che fornisce messaggi di errore reali è correlata alla classe dei tipi e alle funzionalità di sistema di tipo avanzato, non agli operatori definiti dall'utente. È un problema difficile sovraccarico, non definizione dell'utente.
Philip JF,

25

Ignoriamo per ora l'intero argomento "Gli operatori vengono abusati di danneggiare la leggibilità" e concentriamoci sulle implicazioni del design del linguaggio.

Gli operatori di Infix hanno più problemi delle semplici regole di precedenza (sebbene sia schietto, il collegamento a cui fai riferimento banalizza l'impatto di quella decisione di progettazione). Uno è la risoluzione dei conflitti: cosa succede quando si definisce a.operator+(b)e b.operator+(a)? Preferire l'uno rispetto all'altro porta a infrangere la proprietà commutativa attesa di quell'operatore. Lanciare un errore può portare alla rottura di moduli che altrimenti funzionerebbero una volta insieme. Cosa succede quando inizi a lanciare tipi derivati ​​nel mix?

Il fatto è che gli operatori non sono solo funzioni. Le funzioni sono indipendenti o appartengono alla loro classe, che fornisce una chiara preferenza su quale parametro (se presente) possiede l'invio polimorfico.

E questo ignora i vari problemi di imballaggio e risoluzione che sorgono dagli operatori. Il motivo per cui i progettisti delle lingue (in linea di massima) limitano la definizione dell'operatore infix è perché crea un mucchio di problemi per la lingua fornendo al contempo vantaggi discutibili.

E, francamente, perché sono non banale da implementare.


1
Credo che questo sia il motivo per cui Java non li include, ma i linguaggi che li hanno hanno regole chiare per la precedenza. Penso che sia simile alla moltiplicazione matriciale. Non è commutativo, quindi devi essere consapevole quando stai usando le matrici. Penso che lo stesso valga quando si ha a che fare con le classi, ma sì, sono d'accordo sul fatto che avere un non commutativo +sia un male. Ma è davvero un argomento contro gli operatori definiti dall'utente? Sembra un argomento contro il sovraccarico dell'operatore in generale.
beatgammit,

1
@tjameson - Sì, le lingue hanno regole chiare per la precedenza, per la matematica . Le regole di precedenza per le operazioni matematiche probabilmente non si applicheranno realmente se gli operatori vengono sovraccaricati per cose del genere boost::spirit. Non appena si autorizzano operatori definiti dall'utente che peggiorano poiché non esiste un buon modo per definire la precedenza in matematica. Ne ho scritto un po ' nel contesto di una lingua che cerca specificamente di affrontare i problemi con operatori definiti arbitrariamente.
Telastyn,

22
Chiamo cazzate, signore. C'è vita al di fuori del ghetto OO e le funzioni non appartengono necessariamente a nessun oggetto, quindi trovare la funzione giusta è esattamente lo stesso di trovare l'operatore giusto.
Matthieu M.

1
@matthieuM. - Certamente. Le regole di proprietà e di spedizione sono più uniformi in quelle lingue che non supportano le funzioni dei membri. A un certo punto, però, la domanda originale diventa "perché le lingue non OO sono più comuni?" che è un altro gioco di baseball.
Telastyn,

11
Hai visto come Haskell esegue gli operatori personalizzati? Funzionano esattamente come le normali funzioni, tranne per il fatto che hanno anche una precedenza associata. (In effetti, così possono funzionare le normali funzioni, quindi anche lì non differiscono davvero.) Fondamentalmente, gli operatori sono infissi di default e i nomi sono prefissi, ma questa è l'unica differenza.
Tikhon Jelvis,

19

Penso che rimarrai sorpreso dalla frequenza con cui i sovraccarichi dell'operatore vengono implementati in qualche modo. Ma non sono comunemente usati in molte comunità.

Perché usare ~ per concatenare un array? Perché non usare << come fa Ruby ? Perché i programmatori con cui lavori non sono probabilmente programmatori Ruby. O programmatori D. Quindi cosa fanno quando trovano il tuo codice? Devono cercare il significato del simbolo.

Lavoravo con un ottimo sviluppatore C # che aveva anche un gusto per i linguaggi funzionali. All'improvviso, ha iniziato a introdurre monadi in C # mediante metodi di estensione e usando la terminologia standard delle monadi. Nessuno poteva contestare che parte del suo codice fosse più teso e ancora più leggibile una volta che sapessi cosa significasse, ma ciò significava che tutti dovevano imparare la terminologia della monade prima che il codice avesse senso .

Abbastanza giusto, pensi? Era solo una piccola squadra. Personalmente, non sono d'accordo. Ogni nuovo sviluppatore era destinato a essere confuso da questa terminologia. Non abbiamo abbastanza problemi nell'apprendimento di un nuovo dominio?

D'altra parte, userò felicemente l' ??operatore in C # perché mi aspetto che altri sviluppatori C # sappiano di cosa si tratta, ma non lo sovraccaricherei in un linguaggio che non lo supportava per impostazione predefinita.


Non capisco il tuo esempio Double.NaN, questo comportamento fa parte delle specifiche in virgola mobile ed è supportato in tutte le lingue che ho usato. Stai dicendo che gli operatori personalizzati non sono supportati perché confonderebbero gli sviluppatori che non conoscono la funzionalità? Sembra lo stesso argomento contro l'uso dell'operatore ternario o il tuo ??esempio.
beatgammit,

@tjameson: Hai ragione, in realtà, era un po 'tangenziale al punto che stavo cercando di fare, e non particolarmente ben scritto. Ho pensato al ?? esempio mentre lo stavo scrivendo e preferisco quell'esempio. Rimozione del paragrafo double.NaN.
pdr

6
Penso che il tuo punto "ogni nuovo sviluppatore era destinato a essere confuso da questa terminologia" è un po 'poco brillante, preoccuparsi della curva di apprendimento dei nuovi sviluppatori per la tua base di codice è per me come preoccuparsi della curva di apprendimento dei nuovi sviluppatori per un nuova versione di .NET. Penso che sia più importante quando gli sviluppatori lo imparano (nuovo .NET o la tua base di codice), ha senso e mostra valore? In tal caso, la curva di apprendimento ne vale la pena, perché ogni sviluppatore deve solo impararla una volta, sebbene il codice debba essere gestito innumerevoli volte, quindi se migliora il codice è un costo minore.
Jimmy Hoffa,

7
@JimmyHoffa È un costo ricorrente. Pagato in anticipo per ogni nuovo sviluppatore e influisce anche sugli sviluppatori esistenti perché devono supportarlo. C'è anche un costo di documentazione e un elemento di rischio: oggi è abbastanza sicuro, ma a 30 anni di distanza, tutti saranno passati, la lingua e l'applicazione sono ormai "legacy", la documentazione è un enorme mucchio di fumanti e un povero succhiatore rimarrà a strapparsi i capelli per lo splendore dei programmatori che erano troppo pigri per scrivere ".concat ()". Il valore atteso è sufficiente per compensare i costi?
Sylverdrag il

3
Inoltre, l'argomento "Ogni nuovo sviluppatore era destinato a essere confuso da questa terminologia. Non abbiamo abbastanza problemi nell'apprendimento di un nuovo dominio?" avrebbe potuto essere applicato a OOP negli anni '80, programmazione strutturata prima, o IoC 10 anni fa. È tempo di smettere di usare questo argomento fallace.
Mauricio Scheffer,

11

Mi vengono in mente alcuni motivi:

  • Non sono banali da implementare : consentire a operatori personalizzati arbitrari di rendere il compilatore molto più complesso, soprattutto se si consentono regole di precedenza, fissità e arity definite dall'utente. Se la semplicità è una virtù, il sovraccarico dell'operatore ti sta allontanando da un buon design del linguaggio.
  • Vengono abusati , soprattutto dai programmatori che pensano che sia "bello" ridefinire gli operatori e iniziare a ridefinirli per ogni tipo di classe personalizzata. In poco tempo, il tuo codice è disseminato di un carico di simboli personalizzati che nessun altro può leggere o comprendere perché gli operatori non seguono le regole convenzionali ben comprese. Non compro l'argomento "DSL", a meno che il tuo DSL non sia un sottoinsieme della matematica :-)
  • Essi fanno male leggibilità e la manutenibilità - se gli operatori vengono sovrascritte con regolarità, può diventare difficile da individuare quando si utilizza questo strumento, e programmatori sono costretti a chiedere continuamente se stessi ciò che un operatore sta facendo. È molto meglio dare nomi di funzioni significative. Digitare qualche carattere in più è economico, i problemi di manutenzione a lungo termine sono costosi.
  • Possono infrangere le aspettative di prestazioni implicite . Ad esempio, normalmente mi aspetto la ricerca di un elemento in un array O(1). Ma con il sovraccarico dell'operatore, someobject[i]potrebbe facilmente essere O(n)un'operazione a seconda dell'implementazione dell'operatore di indicizzazione.

In realtà, ci sono pochissimi casi in cui il sovraccarico dell'operatore ha usi giustificati rispetto al semplice utilizzo di funzioni regolari. Un esempio legittimo potrebbe essere la progettazione di una classe numerica complessa per l'uso da parte di matematici, che comprendono i modi ben compresi in cui gli operatori matematici sono definiti per numeri complessi. Ma questo non è davvero un caso molto comune.

Alcuni casi interessanti da considerare:

  • Lisps : in generale non distingue affatto tra operatori e funzioni - +è solo una funzione regolare. Puoi definire le funzioni come preferisci (in genere esiste un modo per definirle in spazi dei nomi separati per evitare conflitti con il built-in +), inclusi gli operatori. Ma c'è una tendenza culturale a usare nomi di funzioni significative, quindi questo non viene abusato molto. Inoltre, nel prefisso Lisp la notazione tende ad essere utilizzata esclusivamente, quindi c'è meno valore nello "zucchero sintattico" che i sovraccarichi dell'operatore forniscono.
  • Java : non consente il sovraccarico dell'operatore. Questo a volte è fastidioso (per cose come il caso del numero complesso) ma in media è probabilmente la giusta decisione di progettazione per Java che è intesa come una lingua OOP semplice e generica. Il codice Java è in realtà abbastanza facile da mantenere per gli sviluppatori con competenze medio-basse grazie a questa semplicità.
  • Il C ++ ha un sovraccarico dell'operatore molto sofisticato. A volte questo viene abusato ( cout << "Hello World!"chiunque?) Ma l'approccio ha senso dato il posizionamento di C ++ come linguaggio complesso che consente una programmazione di alto livello pur consentendo di avvicinarsi molto al metal per le prestazioni, quindi è possibile ad esempio scrivere una classe numerica complessa che si comporta esattamente come vuoi senza compromettere le prestazioni. Resta inteso che è tua responsabilità se ti spari a un piede.

8

Poiché questa funzionalità è piuttosto banale da implementare, perché non è più comune?

Non è banale da implementare (se non banalmente implementato). Inoltre, non ti ottiene molto, anche se implementato idealmente: i guadagni di leggibilità dalla terseness sono compensati dalle perdite di leggibilità dovute a familiarità e opacità. In breve, non è comune perché di solito non vale il tempo degli sviluppatori o degli utenti.

Detto questo, posso pensare a tre lingue che lo fanno e lo fanno in diversi modi:

  • Racket, uno schema, quando non è tutto S-expression-y consente e si aspetta che tu scriva ciò che equivale al parser per qualsiasi sintassi che desideri estendere (e fornisce hook utili per rendere trattabile).
  • Haskell, un linguaggio di programmazione puramente funzionale, consente di definire qualsiasi operatore costituito esclusivamente da punteggiatura e consente di fornire un livello di fissità (10 disponibili) e un'associatività. Gli operatori ternari ecc. Possono essere creati da operatori binari e funzioni di ordine superiore.
  • Agda, un linguaggio di programmazione tipizzato in modo dipendente, è estremamente flessibile con gli operatori ( qui la carta ) che consente a if-then e if-then-else di essere definiti come operatori nello stesso programma, ma il suo lexer, parser e valutatore sono tutti fortemente accoppiati di conseguenza.

4
Hai detto che "non è banale" e hai immediatamente elencato tre lingue con alcune implementazioni scandalosamente banali. Quindi, dopo tutto, è abbastanza banale, no?
Logica SK

7

Uno dei motivi principali per cui gli operatori personalizzati sono scoraggiati è perché ogni operatore può significare / fare qualsiasi cosa.

Ad esempio il cstreamsovraccarico di spostamento a sinistra molto criticato.

Quando una lingua consente sovraccarichi dell'operatore, generalmente si incoraggia a mantenere il comportamento dell'operatore simile al comportamento di base per evitare confusione.

Anche gli operatori definiti dall'utente rendono l'analisi molto più difficile, specialmente quando ci sono anche regole di preferenza personalizzate.


6
Non vedo come le parti relative al sovraccarico dell'operatore si applicano alla definizione di operatori completamente nuovi.

Ho visto un ottimo uso del sovraccarico dell'operatore (librerie di algebra lineare) e pessimo come hai detto. Non penso che questo sia un buon argomento contro gli operatori personalizzati. L'analisi può essere un problema, ma è abbastanza ovvio quando è previsto un operatore. Dopo l'analisi, spetta al generatore di codice cercare il significato dell'operatore.
beatgammit,

3
E in cosa differisce dal sovraccarico del metodo?
Jörg W Mittag,

@ JörgWMittag con il metodo che sovraccarica c'è (la maggior parte delle volte) un nome significativo allegato. con un simbolo è più difficile spiegare in modo succinto cosa accadrà
maniaco del cricchetto il

1
@ratchetfreak: Beh. +aggiungi due cose, le -sottrae, le *moltiplica. La mia sensazione è che nessuno costringa il programmatore a fare in modo che la funzione / il metodo addaggiungano effettivamente qualsiasi cosa e doNothingpossano lanciare bombe atomiche. Ed a.plus(b.minus(c.times(d)).times(e)è quindi molto meno leggibile a + (b - c * d) * e(bonus aggiunto - dove nella prima puntura c'è un errore nella trascrizione). Non vedo quanto il primo sia più significativo ...
Maciej Piechotka il

4

Non utilizziamo operatori definiti dall'utente per lo stesso motivo per cui non usiamo parole definite dall'utente. Nessuno chiamerebbe la loro funzione "sworp". L'unico modo per trasmettere il tuo pensiero ad altre persone è usare un linguaggio condiviso. Ciò significa che sia le parole che i segni (operatori) devono essere conosciuti dalla società per la quale stai scrivendo il tuo codice.

Pertanto gli operatori che vedi in uso nei linguaggi di programmazione sono quelli che ci hanno insegnato a scuola (aritmetica) o quelli che sono stati stabiliti nella comunità di programmazione, come dicono gli operatori booleani.


1
Aggiungete a ciò la capacità di scoprire il significato dietro una funzione. Nel caso di "Sworp" puoi facilmente inserirlo in una casella di ricerca e trovare la sua documentazione. Attaccare un operatore in un motore di ricerca è un parco palla completamente diverso. I nostri attuali motori di ricerca fanno semplicemente schifo nel trovare operatori, e in alcuni casi ho perso molto tempo a cercare i significati.
Segna H il

1
Quanto "scuola" conti? Ad esempio, penso che ∈ elemsia un'ottima idea e certamente un operatore che tutti dovrebbero capire, ma altri sembrano non essere d'accordo.
Tikhon Jelvis,

1
Non è il problema con il simbolo. È il problema con la tastiera. Ad esempio, utilizzo la modalità haskell di emacs con la funzione di bellezza Unicode abilitata. E converte automaticamente gli operatori ASCII in simboli Unicode. Ma non sarei in grado di scriverlo se non ci fosse un equivalente ASCII.
Vagif Verdi,

2
I linguaggi naturali sono costruiti solo con "parole definite dall'utente" e nient'altro. E alcune lingue in realtà incoraggiano la formazione di parole personalizzate.
Logica SK

4

Per quanto riguarda le lingue che supportano tale sovraccarico: Scala lo fa, in realtà in modo molto più pulito e migliore può C ++. La maggior parte dei caratteri può essere utilizzata nei nomi delle funzioni, quindi è possibile definire operatori come! + * = ++, se lo si desidera. Esiste un supporto integrato per infix (per tutte le funzioni che accettano un argomento). Penso che anche tu possa definire l'associatività di tali funzioni. Non puoi tuttavia definire la precedenza (solo con brutti trucchi, vedi qui ).


4

Una cosa che non è stata ancora menzionata è il caso di Smalltalk, in cui tutto (inclusi gli operatori) è un messaggio inviato. "Operatori" come +, |e così via, sono in realtà metodi unari.

Tutti i metodi possono essere sovrascritti, quindi a + bsignifica aggiunta di numeri interi se ae bsono entrambi numeri interi e significa aggiunta di vettore se sono entrambi OrderedCollections.

Non ci sono regole di precedenza, poiché si tratta solo di chiamate al metodo. Ciò ha un'importante implicazione per la notazione matematica standard: 3 + 4 * 5significa (3 + 4) * 5, no 3 + (4 * 5).

(Questo è un grosso ostacolo per i neofiti di Smalltalk. La violazione delle regole matematiche rimuove un caso speciale, in modo che tutta la valutazione del codice proceda uniformemente da sinistra a destra, rendendo il linguaggio molto più semplice.)


3

Stai combattendo contro due cose qui:

  1. Perché gli operatori esistono in primo luogo nelle lingue?
  2. Qual è la virtù degli operatori rispetto a funzioni / metodi?

Nella maggior parte delle lingue, gli operatori non sono realmente implementati come semplici funzioni. Potrebbero avere alcune impalcature di funzioni, ma il compilatore / runtime è esplicitamente consapevole del loro significato semantico e di come tradurli in modo efficiente nel codice macchina. Questo è molto più vero anche rispetto alle funzioni integrate (motivo per cui la maggior parte delle implementazioni non include anche tutte le spese generali di chiamata delle funzioni nella loro implementazione). La maggior parte degli operatori sono astrazioni di livello superiore su istruzioni primitive presenti nelle CPU (motivo per cui la maggior parte degli operatori è aritmetica, booleana o bit a bit). Potresti modellarle come funzioni "speciali" (chiamale "primitive" o "builtin" o "native" o altro), ma per fare ciò richiede genericamente una serie molto solida di semantiche per definire tali funzioni speciali. L'alternativa è avere operatori integrati che sembrano semanticamente degli operatori definiti dall'utente, ma che altrimenti invocano percorsi speciali nel compilatore. Ciò è in contrasto con la risposta alla seconda domanda ...

A parte il problema della traduzione automatica che ho menzionato sopra, a livello sintattico gli operatori non sono molto diversi dalle funzioni. Stanno distinguendo le caratteristiche che tendono ad essere concise e simboliche, il che suggerisce una significativa caratteristica aggiuntiva che devono essere utili: devono avere ampiamente compreso significato / semantica per gli sviluppatori. I simboli brevi non trasmettono molto significato a meno che non sia abbreviazione di una serie di semantiche che sono già state comprese. Ciò rende gli operatori definiti dall'utente intrinsecamente inutili, poiché per loro stessa natura non sono così ampiamente compresi. Hanno senso tanto quanto i nomi di una o due lettere.

I sovraccarichi dell'operatore di C ++ forniscono terreno fertile per esaminarlo. La maggior parte degli "abusi" del sovraccarico dell'operatore si presenta sotto forma di sovraccarichi che rompono parte del contratto semantico che è ampiamente compreso (un esempio classico è un sovraccarico dell'operatore + tale che a + b! = B + a, o dove + modifica uno dei suoi operandi).

Se guardi Smalltalk, che consente il sovraccarico degli operatori e gli operatori definiti dall'utente, puoi vedere come una lingua potrebbe usarlo e quanto sarebbe utile. In Smalltalk, gli operatori sono semplicemente metodi con proprietà sintattiche diverse (vale a dire, sono codificati come binari infix). Il linguaggio utilizza "metodi primitivi" per operatori e metodi speciali accelerati. Si scopre che pochi se vengono creati operatori definiti dall'utente, e quando lo sono, tendono a non abituarsi tanto quanto probabilmente l'autore ha previsto che fossero utilizzati. Anche l'equivalente di un sovraccarico dell'operatore è raro, poiché è soprattutto una perdita netta definire una nuova funzione come operatore anziché come metodo, poiché quest'ultima consente un'espressione della semantica della funzione.


1

Ho sempre trovato che i sovraccarichi dell'operatore in C ++ sono una scorciatoia conveniente per un team di sviluppatori singoli, ma che causa confusione a lungo termine semplicemente perché le chiamate al metodo vengono "nascoste" in un modo non facile per strumenti come doxygen da separare e le persone devono comprendere i modi di dire per usarli correttamente.

A volte è molto più difficile dare un senso a quello che ti aspetteresti, anche. Una volta, in un grande progetto C ++ multipiattaforma, ho deciso che sarebbe stata una buona idea normalizzare il modo in cui i percorsi sono stati creati creando un FilePathoggetto (simile Fileall'oggetto Java ), che avrebbe avuto l'operatore / usato per concatenare un altro parte del percorso (in modo da poter fare qualcosa di simile File::getHomeDir()/"foo"/"bar"e farebbe la cosa giusta su tutte le nostre piattaforme supportate). Tutti quelli che l'hanno visto direbbero essenzialmente: "Che diavolo? Divisione delle corde? ... Oh, è carino, ma non mi fido che faccia la cosa giusta."

Allo stesso modo, ci sono molti casi nella programmazione grafica o in altre aree in cui la matematica vettoriale / matrice si verifica molto in cui è allettante fare cose come Matrix * Matrix, Vector * Vector (punto), Vector% Vector (croce), Matrix * Vector ( matrice di trasformazione), Matrix ^ Vector (trasformazione di matrice in casi speciali ignorando la coordinata omogenea - utile per le normali di superficie) e così via, ma mentre fa risparmiare un po 'di tempo di analisi per la persona che ha scritto la libreria matematica vettoriale, finisce solo confondere la questione più per gli altri. Non ne vale la pena.


0

I sovraccarichi dell'operatore sono una cattiva idea per lo stesso motivo per cui i sovraccarichi del metodo sono una cattiva idea: lo stesso simbolo sullo schermo avrebbe significati diversi a seconda di ciò che lo circonda. Questo rende più difficile la lettura casuale.

Poiché la leggibilità è un aspetto critico della manutenibilità, è necessario evitare sempre il sovraccarico (tranne in alcuni casi molto speciali). È molto meglio che ogni simbolo (operatore o identificatore alfanumerico) abbia un significato unico che si distingue da solo.

Per illustrare: quando si legge un codice sconosciuto, se si incontra un nuovo identificatore alfanumerico che non si conosce, almeno si ha il vantaggio di sapere che non lo si conosce. Puoi quindi andare a cercarlo. Se, tuttavia, vedi un identificatore o un operatore comune di cui conosci il significato, avrai molte meno probabilità di notare che in realtà è stato sovraccaricato per avere un significato completamente diverso. Per sapere quali operatori sono stati sovraccaricati (in una base di codice che ha fatto ampio uso del sovraccarico), è necessario avere una conoscenza pratica del codice completo, anche se si desidera leggere solo una piccola parte di esso. Ciò renderebbe difficile portare nuovi sviluppatori alla velocità su quel codice e impossibile coinvolgere le persone per un piccolo lavoro. Questo può essere utile per la sicurezza del lavoro del programmatore, ma se sei responsabile del successo della base di codice, dovresti evitare questa pratica a tutti i costi.

Poiché gli operatori sono di piccole dimensioni, sovraccaricare gli operatori consentirebbe un codice più denso, ma rendere il codice denso non è un vero vantaggio. Una riga con il doppio della logica richiede il doppio del tempo di lettura. Al compilatore non importa. L'unico problema è la leggibilità umana. Poiché la compattazione del codice non migliora la leggibilità, la compattezza non offre alcun vantaggio. Vai avanti e occupati dello spazio e assegna alle operazioni uniche un identificatore univoco e il tuo codice avrà più successo a lungo termine.


"lo stesso simbolo sullo schermo avrebbe significati diversi a seconda di ciò che lo circonda" - è già il caso di molti operatori nella maggior parte delle lingue.
rkj,

-1

Difficoltà tecniche per gestire la precedenza e analisi complesse messe da parte, penso che ci siano alcuni aspetti di ciò che un linguaggio di programmazione deve essere considerato.

Gli operatori sono in genere costrutti logici brevi che sono ben definiti e documentati nel linguaggio principale (confronta, assegna ...). In genere sono anche difficili da capire senza documentazione (confronta a^bcon xor(a,b)per esempio). Esiste un numero piuttosto limitato di operatori che potrebbero effettivamente avere senso nella normale programmazione (>, <, =, + ecc.).

La mia idea è che è meglio attenersi a una serie di operatori ben definiti in una lingua, quindi consentire il sovraccarico degli operatori di tali operatori (data una leggera raccomandazione che gli operatori dovrebbero fare la stessa cosa, ma con un tipo di dati personalizzato).

I tuoi casi d'uso di ~e |sarebbero effettivamente possibili con un semplice sovraccarico dell'operatore (C #, C ++, ecc.). DSL è un'area di utilizzo valida, ma probabilmente una delle uniche aree valide (dal mio punto di vista). Tuttavia, penso che ci siano strumenti migliori per creare nuove lingue all'interno. L'esecuzione di un vero linguaggio DSL in un'altra lingua non è così difficile utilizzando uno di questi strumenti di compilatore-compilatore. Lo stesso vale per "Estendi argomento LUA". Molto probabilmente una lingua viene definita principalmente per risolvere i problemi in un modo specifico, non per costituire una base per le lingue secondarie (esistono eccezioni).


-1

Un altro fattore è che non è sempre semplice definire un'operazione con gli operatori disponibili. Voglio dire, sì, per qualsiasi tipo di numero, l'operatore '*' può avere un senso e di solito sono implementati nella lingua o nei moduli esistenti. Ma nel caso delle classi complesse tipiche che è necessario definire (cose come ShipingAddress, WindowManager, ObjectDimensions, PlayerCharacter, ecc.) Quel comportamento non è chiaro ... Cosa significa aggiungere o sottrarre un numero a un indirizzo? Moltiplicare due indirizzi?

Certo, puoi definire che l'aggiunta di una stringa a una classe ShippingAddress significa un'operazione personalizzata come "Sostituisci la riga 1 nell'indirizzo" (invece della funzione "setLine1") e l'aggiunta di un numero è "Sostituisci codice postale" (invece di "setZipCode") , ma il codice non è molto leggibile e confuso. Generalmente pensiamo che gli operatori vengano utilizzati in tipi / classi di base, poiché il loro comportamento è intuitivo, chiaro e coerente (almeno una volta che hai familiarità con la lingua). Pensa in tipi come Integer, String, ComplexNumbers, ecc.

Quindi, anche se la definizione di operatori può essere molto utile in alcuni casi specifici, la loro implementazione nel mondo reale è piuttosto limitata, poiché il 99% dei casi in cui ciò rappresenterà una chiara vittoria è già implementato nel pacchetto linguistico di base.

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.