Pourquoi existe-t-il si peu de langues avec un opérateur de type variable?

46

Je le pense de cette façon:

<?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);
?>

Mais aussi de cette façon:

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

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

Cela ne semble pas être le cas pour toutes les langues - existe-t-il une bonne raison pour que ce ne soit pas le cas?

kgongonowdoe
la source
28
En général, ce que vous voulez faire serait couvert par tous les langages qui prennent en charge une forme de méta-programmation avec des sortes de lambdas, des fermetures ou des fonctions anonymes, ce qui serait le moyen habituel d'implémenter de telles fonctionnalités. Avec des langues où les méthodes sont des citoyens de première classe, vous seriez en mesure de les utiliser plus ou moins identiques à des variables. Bien que ce ne soit pas exactement la syntaxe simple que vous utilisez ici, car dans la plupart des langages de ce type, vous devez indiquer clairement que vous souhaitez réellement appeler la méthode stockée dans la variable.
Thorsten Müller
7
@MartinMaat Les langages fonctionnels le font beaucoup.
Thorbjørn Ravn Andersen
7
dans haskell, les opérateurs sont des fonctions comme n'importe quelle autre fonction. le type (+)est Num a => a -> a -> aIIRC. vous pouvez également définir des fonctions afin qu'elles puissent être écrites en caractères infixés ( a + bau lieu de (+) a b)
sara
5
@enderland: Votre modification a complètement modifié l'objectif de la question. Il est passé de demander s'il existe des langues, à demander pourquoi il en existe si peu. Je pense que votre édition aura pour résultat beaucoup de lecteurs confus.
Bryan Oakley
5
@enderland: Bien que ce soit vrai, changer complètement de sujet ne sert qu'à confondre. Si c'est hors sujet, la communauté votera pour le fermer. Aucune des réponses (au moment où j'écris ceci) n'a de sens pour la question telle qu'elle est écrite.
Bryan Oakley

Réponses:

103

Les opérateurs ne sont que des fonctions sous des noms amusants, avec une syntaxe spéciale.

Dans de nombreuses langues, aussi variées que C ++ et Python, vous pouvez redéfinir les opérateurs en redéfinissant les méthodes spéciales de votre classe. Ensuite, les opérateurs standard (par exemple +) fonctionnent selon la logique que vous indiquez (par exemple, concaténer des chaînes ou ajouter des matrices, etc.).

Comme ces fonctions définissant des opérateurs ne sont que des méthodes, vous pouvez les transmettre comme une fonction:

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

Autres langues vous permet de définir directement de nouveaux opérateurs en tant que fonctions et de les utiliser sous forme infixe.

-- 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

Malheureusement, je ne suis pas sûr si PHP supporte quelque chose comme ça, et si le supporter serait une bonne chose. Utilisez une fonction simple, c'est plus lisible que $foo $operator $bar.

9000
la source
2
@tac: Oui, c’est bien et cela peut même être porté en d’autres langues :) En ce qui concerne Haskell, ce qui me manque le plus, c’est que ce département est l’ $opérateur qui supprime les parenthèses (en particulier les multiples imbriqués) - mais cela ne peut fonctionner qu'avec -variadic fonctions, excluant ainsi par exemple Python et Java. La composition de fonction unaire OTOH peut très bien être faite .
9000
6
Je n'ai jamais été un partisan de la surcharge d'opérateurs, car oui, un opérateur est simplement une fonction avec une syntaxe spéciale, mais il existe un contrat implicite qui est généralement attribué à des opérateurs sans fonctions. "+", par exemple, a certaines attentes - priorité des opérateurs, commutativité, etc. - et aller à l'encontre de ces attentes est un moyen sûr de semer la confusion parmi les utilisateurs et de générer des bugs. Une raison pour laquelle, bien que j'aime le javascript, j'aurais préféré qu'ils distinguent + pour l'addition et la concaténation. Perl l'avait juste là.
fool4jesus
4
Notez que Python a un operatormodule standard qui vous permet d’écrire action = operator.addet qu’il fonctionne pour tous les types qui définissent +(pas seulement int).
dan04
3
Dans Haskell +, la classe de types Num vous permet de la mettre en œuvre +pour tout nouveau type de données que vous créez, mais de la même manière que vous faites autre chose, par exemple fmap pour un foncteur. Il est tellement naturel que Haskell permette aux opérateurs surchargés de travailler dur pour ne pas le permettre!
Martin Capodici
17
Vous ne savez pas pourquoi les gens sont obsédés par la surcharge. La question initiale ne concerne pas la surcharge. Il me semble que les opérateurs sont des valeurs de première classe. Pour écrire $operator1 = +et utiliser ensuite une expression, vous n'avez pas du tout besoin de surcharger l'opérateur !
Andres F.
16

Il y a beaucoup de langues qui permettent une sorte de métaprogrammation . En particulier, je suis surpris de ne voir aucune réponse parler de la famille de langues Lisp .

De wikipedia:

La métaprogrammation est l'écriture de programmes informatiques avec la capacité de traiter les programmes comme leurs données.

Plus tard dans le texte:

Lisp est probablement le langage par excellence avec les installations de métaprogrammation, à la fois en raison de sa priorité historique et de la simplicité et de la puissance de sa métaprogrammation.

Langues Lisp

Suit une introduction rapide à Lisp.

Une façon de voir le code consiste à suivre une suite d'instructions: faites ceci, puis faites-le, puis procédez comme suit ... Ceci est une liste! Une liste de choses à faire pour le programme. Et bien sûr, vous pouvez avoir des listes dans des listes pour représenter des boucles, etc.

Si nous représentons une liste contenant les éléments a, b, c, d comme celui - ci: (ABCD) nous obtenons quelque chose qui ressemble à un appel de fonction Lisp, où aest la fonction, et b, c, dsont les arguments. Si fait le typique "Hello World!" programme pourrait être écrit comme suit:(println "Hello World!")

Bien sûr b, cou dpourrait être des listes qui donnent aussi quelque chose. Ce qui suit: (println "I can add :" (+ 1 3) )serait alors imprimer "" Je peux ajouter: 4 ".

Ainsi, un programme est une série de listes imbriquées et le premier élément est une fonction. La bonne nouvelle est que nous pouvons manipuler des listes! Nous pouvons donc manipuler les langages de programmation.

L'avantage Lisp

Lisps ne sont pas tant des langages de programmation qu'une boîte à outils permettant de les créer. Un langage de programmation programmable.

Il est non seulement beaucoup plus facile avec Lisps de créer de nouveaux opérateurs, il est également presque impossible d'écrire certains opérateurs dans d'autres langues car les arguments sont évalués lorsqu'ils sont transmis à la fonction.

Par exemple, dans un langage de type C, supposons que vous souhaitiez écrire ifvous-même un opérateur, par exemple:

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

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

Dans ce cas, les deux arguments seront évalués et imprimés, dans un ordre dépendant de l'ordre d'évaluation des arguments.

En Lisps, écrire un opérateur (on appelle ça une macro) et écrire une fonction est à peu près la même chose et utilisé de la même manière. La principale différence est que les paramètres d'une macro ne sont pas évalués avant d'être passés en tant qu'arguments à la macro. Ceci est essentiel pour pouvoir écrire certains opérateurs, comme ifci - dessus.

Langues du monde réel

Montrer comment est exactement un peu hors de portée ici, mais je vous encourage à essayer de programmer dans un Lisp pour en savoir plus. Par exemple, vous pouvez regarder:

  • Scheme , un vieux Lisp assez "pur" avec un petit noyau
  • Common Lisp, un Lisp plus grand avec un système objet bien intégré et de nombreuses implémentations (normalisé ANSI)
  • Raquette un Lisp dactylographié
  • Clojure mon préféré, les exemples ci-dessus étaient du code Clojure. Un Lisp moderne fonctionnant sur la JVM. Il existe également quelques exemples de macros Clojure sur SO (mais ce n’est pas le bon endroit pour commencer. Je commencerais par regarder 4clojure , braveclojure ou clojure koans )).

Oh, au fait, Lisp veut dire LISt Processing.

Concernant vos exemples

Je vais donner des exemples en utilisant Clojure ci-dessous:

Si vous pouvez écrire une addfonction dans Clojure (defn add [a b] ...your-implementation-here... ), vous pouvez le nommer +ainsi (defn + [a b] ...your-implementation-here... ). C’est en fait ce qui est fait dans l’ implémentation réelle (le corps de la fonction est un peu plus impliqué mais la définition est essentiellement la même que celle que j’ai écrite ci-dessus).

Qu'en est-il de la notation infixe? Eh bien Clojure utilise une prefixnotation (ou polonaise), nous pourrions donc créer une infix-to-prefixmacro qui transformerait le code préfixé en code Clojure. Ce qui est en fait étonnamment facile (c’est en fait l’un des exercices de macro dans les koans du clojure)! On peut également le voir à l'état sauvage, par exemple voir Macro Incanter$= .

Voici la version la plus simple des koans expliqués:

(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

Pour pousser plus loin le propos, quelques citations de Lisp :

«Ce qui distingue Lisp, c’est qu’il est conçu pour évoluer. Vous pouvez utiliser Lisp pour définir de nouveaux opérateurs Lisp. Au fur et à mesure que de nouvelles abstractions deviennent populaires (programmation orientée objet, par exemple), il s'avère toujours facile de les implémenter dans Lisp. Comme l’ADN, un tel langage ne se démode pas.

- Paul Graham, ANSI Common Lisp

«Programmer en Lisp, c'est comme jouer avec les forces primordiales de l'univers. Il se sent comme un éclair entre vos doigts. Aucune autre langue ne se sent même proche. "

- Glenn Ehrlich, Route de Lisp

nha
la source
1
Notez que la métaprogrammation, bien qu’intéressante, n’est pas nécessaire pour appuyer la question posée par le PO. Toute langue prenant en charge des fonctions de première classe suffit.
Andres F.
1
Exemple à la question de OP: (let ((opp # '+)) (print (apply opp' (1 2)))))
Kasper van den Berg
1
Aucune mention de Common Lisp?
Coredump
3
Je me souviens d'une discussion en groupe sur les langues, Ken parlait de priorité dans APL et concluait par "je n'utilise presque jamais de parenthèses!" Et quelqu'un du public a crié: "C'est parce que Dick les a toutes utilisées!"
JDługosz
2
Dans un langage de style C ++, vous pouvez réimplémenter if, mais vous devez envelopper les arguments thenet elseavec des lambdas. PHP et JavaScript ont function(), C ++ a lambdas, et il existe une extension Apple à C avec lambdas.
Damian Yerrick
9

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

La plupart des implémentations de langages ont une étape où un analyseur analyse votre code et construit un arbre à partir de celui-ci. Ainsi, par exemple, l'expression 5 + 5 * 8serait analysée comme

  +
 / \
5   *
   / \
  8   8

grâce à la connaissance du compilateur sur les précédents. Si vous introduisiez des variables à la place des opérateurs, il ne saurait connaître le bon ordre des opérations avant d'exécuter le code. Pour la plupart des implémentations, ce serait un problème sérieux, donc la plupart des langues ne le permettent pas.

Vous pouvez bien sûr concevoir un langage dans lequel l'analyseur syntaxique analyse simplement ce qui précède comme une séquence d'expressions et d'opérateurs, à trier et à évaluer au moment de l'exécution. Vraisemblablement, il n'y a tout simplement pas beaucoup d'applications pour cela.

De nombreux langages de script permettent l'évaluation d'expressions arbitraires (ou au moins d'expressions arithmétiques arbitraires comme dans le cas de expr) lors de l'exécution. Là, vous pouvez combiner vos chiffres et vos opérateurs en une seule expression et laisser le langage l’évaluer. En PHP (et beaucoup d'autres), cette fonction est appelée eval.

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

Il existe également des langages permettant la génération de code au moment de la compilation. L’ expression mixin de D me vient à l’esprit, où je crois que vous pourriez écrire quelque chose comme

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

Ici operator1et operator2devraient être des constantes de chaîne qui sont connues au moment de la compilation, par exemple des paramètres de modèle. number1, number2et number3ont été laissés comme variables d’exécution normales.

D'autres réponses ont déjà abordé les différentes manières dont un opérateur et une fonction sont plus ou moins la même chose, en fonction de la langue. Mais il existe généralement une différence syntaxique entre un symbole d'opérateur infix intégré, tel que, +et un appelable, tel que nommé operator1. Je vais laisser les détails à ces autres réponses.

MvG
la source
+1 Vous devriez commencer par "c'est possible en PHP avec la eval()construction de langage " ... Techniquement, il fournit exactement le résultat souhaité dans la question.
Armfoot
@Armfoot: Il est difficile de dire où se situe le centre de la question. Le titre met l'accent sur l'aspect «variable de type opérateur» et evalne répond pas à cet aspect, car evalles opérateurs ne sont que des chaînes. Par conséquent, j'ai commencé par expliquer pourquoi les variables de type opérateur posaient problème, avant de commencer à discuter des alternatives.
MvG
Je comprends vos arguments, mais considérez qu'en insérant tout dans une chaîne, vous sous-entendez que les types de variable ne sont plus pertinents (depuis que PHP était utilisé comme exemple, cela semble être l'objectif principal de la question), et finalement, vous peut les placer de la même manière que si certaines de ces variables étaient de type "opérateur" tout en obtenant le même résultat ... C'est pourquoi je pense que votre suggestion fournit la réponse la plus précise à la question.
Armfoot
2

Algol 68 avait exactement cette caractéristique. Votre exemple dans Algol 68 ressemblerait à ceci:

int nombre1 = 5;                              ¢ (type 'Int') ¢
op opérateur1 = int ( int a , b ) a + b ; ¢ (type "opérateur" non existant) ¢ opérateur
prio1 = 4; int nombre2 = 5;                              ¢ (Type 'Int') ¢ op opérateur2 = int ( int a , b ) a * b ;  ¢

(Tapez 'Opérateur' inexistant) ¢ Opérateur
prio2 = 5; int nombre3 = 8;                              ¢ (Type 'Int') ¢

int essai = number1 opérateur1 number2 opérateur2 number3 ; ¢ 5 + 5 * 8. ¢

var_dump ( test );

Votre deuxième exemple ressemblerait à ceci:

int nombre4 = 9;
op opérateur3 = booléen ( int a , b ) a < b ; opérateur
prio3 = 3; si nombre1 $ opérateur3 nombre4 alors ¢ 5 <9 (vrai) ¢ impression ( vrai ) fi


Vous remarquerez que les symboles d'opérateur sont des corps de méthode définis et affectés contenant l'opération souhaitée. Les opérateurs et leurs opérandes sont tous dactylographiés et des priorités peuvent être attribuées aux opérateurs afin que l'évaluation se déroule dans le bon ordre. Vous remarquerez peut-être également qu'il existe une légère différence de police de caractères entre le symbole d'opérateur et un symbole de variable.

En fait, bien que la langue soit écrite en utilisant la police de caractères, les machines du jour ne pouvaient pas gérer les polices (bande de papier et cartes perforées), et le strophage était utilisé. Le programme serait probablement entré comme:

'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'

Vous pouvez également jouer à des jeux intéressants avec le langage lorsque vous pouvez définir vos propres symboles pour les opérateurs, que j'ai exploités une fois, il y a de nombreuses années ... [2].


Références:

[1] Introduction informelle à Algol 68 par CHLindsey et SG van der Meulen, Hollande du Nord, 1971 .

[2] Algol 68 Phrases, outil d'aide à l'écriture de compilateurs dans Algol 68 , BC Tompsett, Conférence internationale sur les applications d'Algol 68, Université de East Anglia, Norwich, Royaume-Uni, 1976 ..

Brian Tompsett - 莱恩
la source