Perché esistono così poche lingue con un "operatore" di tipo variabile?

47

Intendo in questo modo:

<?php
    $number1 = 5;   // (Type 'Int')
    $operator1 = +; // (Type non-existent 'Operator')
    $number2 = 5;   // (Type 'Int')
    $operator2 = *; // (Type non-existent 'Operator')
    $number3 = 8;   // (Type 'Int')

    $test = $number1 $operator1 $number2 $operator2 $number3; //5 + 5 * 8.

    var_dump($test);
?>

Ma anche in questo modo:

<?php
    $number1 = 5;
    $number3 = 9;
    $operator1 = <;

    if ($number1 $operator1 $number3) { //5 < 9 (true)
        echo 'true';
    }
?>

Non sembra che alcune lingue abbiano questo - c'è una buona ragione per cui non lo fanno?

    
posta kgongonowdoe 22.03.2016 - 15:48
fonte

4 risposte

103

Gli operatori sono solo funzioni con nomi divertenti, con qualche sintassi speciale in giro.

In molte lingue, come C ++ e Python, è possibile ridefinire gli operatori sovrascrivendo i metodi speciali della classe. Quindi gli operatori standard (ad esempio + ) funzionano in base alla logica fornita dall'utente (ad esempio concatenando stringhe o aggiungendo matrici o qualsiasi altra cosa).

Poiché tali funzioni di definizione dell'operatore sono solo metodi, è possibile passarle come se si trattasse di una funzione:

# python
action = int.__add__
result = action(3, 5)
assert result == 8

Altre lingue ti permettono di definire direttamente nuovi operatori come funzioni e di usarli in forma infissa.

-- haskell
plus a b = a + b  -- a normal function
3 'plus' 5 == 8 -- True

(+++) a b = a + b  -- a funny name made of non-letters
3 +++ 5 == 8 -- True

let action = (+)
1 'action' 3 == 4 -- True

Sfortunatamente, non sono sicuro che PHP supporti qualcosa di simile, e se supportarlo sarebbe una buona cosa. Utilizza una funzione semplice, è più leggibile di $foo $operator $bar .

    
risposta data 22.03.2016 - 16:00
fonte
15

Esistono molte lingue che consentono un tipo di metaprogramming . In particolare, sono sorpreso di vedere nessuna risposta parlando della Lisp famiglia di lingue.

Da wikipedia:

Metaprogramming is the writing of computer programs with the ability to treat programs as their data.

Più tardi nel testo:

Lisp is probably the quintessential language with metaprogramming facilities, both because of its historical precedence and because of the simplicity and power of its metaprogramming.

Lingue Lisp

Segue una breve introduzione inventata a Lisp.

Un modo per vedere il codice è come una serie di istruzioni: fai questo, poi fallo, poi fai quest'altra cosa ... Questa è una lista! Una lista di cose da fare per il programma. E ovviamente puoi avere liste all'interno di liste per rappresentare loop e così via ...

Se rappresentiamo una lista contenente gli elementi a, b, c, d in questo modo: (abcd) otteniamo qualcosa che assomiglia a una chiamata di funzione Lisp, dove a è la funzione e b , c , d sono gli argomenti. Se è vero il tipico "Hello World!" il programma potrebbe essere scritto in questo modo: (println "Hello World!")

Ovviamente b , c o d potrebbero essere elenchi che valutano anche qualcosa. Quanto segue: (println "I can add :" (+ 1 3) ) quindi stampa "" Posso aggiungere: 4 ".

Quindi, un programma è una serie di liste annidate e il primo elemento è una funzione. La buona notizia è che possiamo manipolare le liste! Quindi possiamo manipolare i linguaggi di programmazione.

Il vantaggio Lisp

Lisps are not so much programming languages as much as a toolkit for making programming languages. A programmable programming language.

Questo non è solo molto più facile in Lisps per creare nuovi operatori, è anche quasi impossibile scrivere alcuni operatori in altre lingue perché gli argomenti vengono valutati quando vengono passati alla funzione.

Per esempio in un linguaggio simile a C diciamo che vuoi scrivere un operatore if tu stesso, qualcosa del tipo:

my-if(condition, if-true, if-false)

my-if(false, print("I should not be printed"), print("I should be printed"))

In questo caso entrambi gli argomenti saranno valutati e stampati, in un ordine dipendente dall'ordine di valutazione degli argomenti.

In Lisps, scrivere un operatore (noi lo chiamiamo macro) e scrivere una funzione è circa la stessa cosa e usato allo stesso modo. La principale differenza è che i parametri di una macro sono non valutati prima di essere passati come argomenti alla macro. Questo è essenziale per poter scrivere alcuni operatori, come if sopra.

Lingue reali

Mostra quanto esattamente è un po 'fuori portata qui, ma ti incoraggio a provare a programmare in un Lisp per saperne di più. Ad esempio potresti dare un'occhiata a:

  • Scheme , un Lisp vecchio, abbastanza "puro" con un piccolo nucleo
  • Common Lisp, un Lisp più grande con un sistema di oggetti ben integrato e molte implementazioni (è standardizzato ANSI)
  • Racket un Lisp digitato
  • Clojure il mio preferito, gli esempi sopra erano codice Clojure. Un Lisp moderno in esecuzione sulla JVM. Ci sono alcuni esempi di macro Clojure su SO (ma questo non è il posto giusto per iniziare. 4clojure , braveclojure o clojure koans all'inizio)).

Oh, a proposito, Lisp significa elaborazione LISt.

Riguardo ai tuoi esempi

Fornirò degli esempi usando Clojure di seguito:

Se puoi scrivere una funzione add in Clojure (defn add [a b] ...your-implementation-here... ) , puoi chiamarla + come (defn + [a b] ...your-implementation-here... ) . Questo è in effetti ciò che viene fatto nel reale implementazione (il corpo della funzione è un po 'più coinvolto, ma la definizione è essenzialmente la stessa di cui ho scritto sopra).

E la notazione infix? Well Clojure usa una notazione prefix (o Polish), quindi potremmo creare una macro infix-to-prefix che trasformerebbe il codice prefissato in codice Clojure. Che è in realtà sorprendentemente facile (in realtà è uno dei esercizi macro nei koan del clojure)! Può anche essere visto in natura, ad esempio vedi Incanter $= macro .

Ecco la versione più semplice dei koans spiegata:

(defmacro infix [form]
  (list (second form) (first form) (nth form 2)))

;; takes a form (ie. some code) as parameter
;; and returns a list (ie. some other code)
;; where the first element is the second element from the original form
;; and the second element is the first element from the original form
;; and the third element is the third element from the original form (indexes start at 0)
;; example :
;; (infix (9 + 1))
;; will become (+ 9 1) which is valid Clojure code and will be executed to give 10 as a result

Per aumentare ulteriormente il punteggio, alcune virgolette Lisp :

“Part of what makes Lisp distinctive is that it is designed to evolve. You can use Lisp to define new Lisp operators. As new abstractions become popular (object-oriented programming, for example), it always turns out to be easy to implement them in Lisp. Like DNA, such a language does not go out of style.”

— Paul Graham, ANSI Common Lisp

“Programming in Lisp is like playing with the primordial forces of the universe. It feels like lightning between your fingertips. No other language even feels close.”

— Glenn Ehrlich, Road to Lisp

    
risposta data 23.03.2016 - 11:08
fonte
9

$test = $number1 $operator1 $number2 $operator2 $number3;

La maggior parte delle implementazioni linguistiche hanno un passo in cui un parser analizza il tuo codice e ne costruisce un albero. Ad esempio, l'espressione 5 + 5 * 8 viene analizzata come

  +
 / \
5   *
   / \
  8   8

grazie alla conoscenza del compilatore sulle precedenze. Se le avete alimentate con variabili al posto di operatori, non conoscerebbe l'ordine corretto delle operazioni prima di eseguire il codice. Per la maggior parte delle implementazioni sarebbe un problema serio, quindi molte lingue non lo consentono.

Potresti ovviamente concepire una lingua in cui il parser analizza solo quanto sopra come una sequenza di espressioni e operatori, da ordinare e valutare in fase di runtime. Presumibilmente non c'è molta applicazione per questo.

Molti linguaggi di scripting consentono la valutazione di espressioni arbitrarie (o perlomeno di espressioni aritmetiche arbitrarie come nel caso di expr ) in fase di esecuzione. Lì potresti semplicemente combinare i tuoi numeri e operatori in un'unica espressione e lasciare che sia il linguaggio a valutarlo. In PHP (e molti altri) questa funzione è chiamata eval .

$test = eval("$number1 $operator1 $number2 $operator2 $number3");

Esistono anche linguaggi che consentono la generazione del codice in fase di compilazione. Mi viene in mente l'espressione mixin expression in D , in cui credo che potresti scrivere qualcosa di simile

test = mixin("number1 " + operator1 + " number2 " + operator2 + "number3");

Qui operator1 e operator2 dovrebbero essere costanti stringa che sono note al momento della compilazione, ad es. parametri del modello. number1 , number2 e number3 sono stati lasciati come normali variabili di runtime.

Altre risposte hanno già discusso i vari modi in cui un operatore e una funzione sono più o meno la stessa cosa, a seconda della lingua. Ma di solito c'è una differenza sintattica tra un simbolo di operatore infisso incorporato come + e un callable chiamato come operator1 . Lascerò i dettagli a quelle altre risposte.

    
risposta data 23.03.2016 - 14:04
fonte
2

Algol 68 aveva esattamente questa caratteristica. Il tuo esempio in Algol 68 sarebbe simile a questo:

int number1 = 5;                              ¢ (Type 'Int') ¢
op operator1 = int (int a,b) a + b; ¢ (Type non-existent 'Operator') ¢
prio operator1 = 4;
int number2 = 5;                              ¢ (Type 'Int') ¢
op operator2 = int (int a,b) a * b;  ¢ (Type non-existent 'Operator') ¢
prio operator2 = 5;
int number3 = 8;                              ¢ (Type 'Int') ¢

int test = number1 operator1 number2 operator2 number3; ¢ 5 + 5 * 8. ¢

var_dump(test);

Il tuo secondo esempio sarà simile a questo:

int number4 = 9;
op operator3 = bool (int a,b) a < b;
prio operator3 = 3;
if number1 $operator3 number4 then ¢ 5 < 9 (true) ¢
print(true)
fi

Noterai che i simboli dell'operatore sono definiti e i corpi dei metodi assegnati che contengono l'operazione desiderata. Gli operatori e il loro operando vengono tutti digitati e agli operatori possono essere assegnate priorità in modo che la valutazione avvenga nell'ordine corretto. Potresti anche notare che c'è una leggera differenza nel font tra il simbolo dell'operatore e un simbolo di variabile.

In realtà, sebbene la lingua sia scritta usando il font, le macchine del giorno non sono in grado di gestire i font (nastro di carta e schede perforate) e stropping è stato utilizzato. Il programma verrebbe probabilmente inserito come:

'INT' NUMBER4 = 9;
'OP' 'OPERATOR3' = 'BOOL' ('INT' A,B) A < B;
'PRIO' 'OPERATOR3' = 3;
'IF' NUMBER1 'OPERATOR3' NUMBER4 'THEN' 'C' 5 < 9 'C'
PRINT('TRUE')
'FI'

Puoi anche giocare a giochi interessanti con la lingua quando puoi definire i tuoi simboli per gli operatori, che ho sfruttato una volta, molti anni fa ... [2].

References:

[1] Introduzione informale ad Algol 68 di CH Lindsey e SG van der Meulen, Olanda del Nord, 1971 .

[2] Algol 68 Phrases, uno strumento per aiutare la scrittura del compilatore in Algol 68 , B.C. Tompsett, Conferenza internazionale sulle applicazioni di Algol 68, all'Università di East Anglia, Norwich, Regno Unito, 1976. .

    
risposta data 24.03.2016 - 16:20
fonte

Leggi altre domande sui tag