Pourquoi exactement est-ce que le mal est eval?

Je sais que les programmeurs Lisp et Scheme disent généralement eval doit être évité, sauf si cela est ssortingctement nécessaire. J’ai vu la même recommandation pour plusieurs langages de programmation, mais je n’ai pas encore vu de liste d’arguments clairs contre l’utilisation d’ eval . Où puis-je trouver un compte des problèmes potentiels liés à l’utilisation d’ eval ?

Par exemple, je connais les problèmes de GOTO dans la programmation procédurale (rend les programmes illisibles et difficiles à gérer, rend les problèmes de sécurité difficiles à trouver, etc.), mais je n’ai jamais vu les arguments contre eval .

Il est intéressant de noter que les mêmes arguments contre GOTO devraient être valables contre les continuations, mais je constate que Schemers, par exemple, ne dira pas que les suites sont «mauvaises» – vous devez juste être prudent lorsque vous les utilisez. Ils sont beaucoup plus susceptibles de désapprouver le code en utilisant eval que sur le code en utilisant des continuations (pour autant que je sache – je peux me tromper).

Il y a plusieurs raisons pour lesquelles il ne faut pas utiliser EVAL .

La principale raison pour les débutants est la suivante: vous n’en avez pas besoin.

Exemple (en supposant Common Lisp):

Évaluez une expression avec différents opérateurs:

 (let ((ops '(+ *))) (dolist (op ops) (print (eval (list op 1 2 3))))) 

C’est mieux écrit comme:

 (let ((ops '(+ *))) (dolist (op ops) (print (funcall op 1 2 3)))) 

Il y a beaucoup d’exemples où les débutants apprenant Lisp pensent qu’ils ont besoin d’ EVAL , mais ils n’en ont pas besoin – puisque les expressions sont évaluées et que l’on peut également évaluer la partie fonction. La plupart du temps, l’utilisation d’ EVAL montre un manque de compréhension de l’évaluateur.

C’est le même problème avec les macros. Souvent, les débutants écrivent des macros, où ils doivent écrire des fonctions – ne pas comprendre à quoi servent les macros et ne pas comprendre qu’une fonction fait déjà le travail.

C’est souvent le mauvais outil pour que le travail utilise EVAL et cela indique souvent que le débutant ne comprend pas les règles d’évaluation habituelles de Lisp.

Si vous pensez avoir besoin d’ EVAL , vérifiez si quelque chose comme FUNCALL , FUNCALL ou APPLY peut être utilisé à la place.

  • FUNCALL – appelle une fonction avec des arguments: (funcall '+ 1 2 3)
  • REDUCE – appelle une fonction sur une liste de valeurs et combine les résultats: (reduce '+ '(1 2 3))
  • APPLY – appelle une fonction avec une liste comme arguments: (apply '+ '(1 2 3)) .

Q: Ai-je vraiment besoin d’éval ou le compilateur / évaluateur est-il déjà ce que je veux vraiment?

Les principales raisons d’éviter EVAL pour les utilisateurs légèrement plus avancés:

  • vous voulez vous assurer que votre code est compilé, car le compilateur peut vérifier le code pour de nombreux problèmes et générer du code plus rapidement, parfois beaucoup plus (c’est le facteur 1000 ;-))

  • Le code construit et devant être évalué ne peut pas être compilé le plus tôt possible.

  • eval de l’entrée utilisateur arbitraire ouvre des problèmes de sécurité

  • une certaine utilisation de l’évaluation avec EVAL peut se produire au mauvais moment et créer des problèmes de construction

Pour expliquer le dernier point avec un exemple simplifié:

 (defmacro foo (ab) (list (if (eql a 3) 'sin 'cos) b)) 

Donc, je peux vouloir écrire une macro basée sur le premier paramètre qui utilise SIN ou COS .

(foo 3 4) fait (sin 4) et (foo 1 4) fait (cos 4) .

Maintenant, nous pouvons avoir:

 (foo (+ 2 1) 4) 

Cela ne donne pas le résultat souhaité.

On peut alors vouloir réparer la macro FOO en évaluant la variable:

 (defmacro foo (ab) (list (if (eql (eval a) 3) 'sin 'cos) b)) (foo (+ 2 1) 4) 

Mais alors cela ne fonctionne toujours pas:

 (defun bar (ab) (foo ab)) 

La valeur de la variable n’est pas connue au moment de la compilation.

Une raison générale importante d’éviter EVAL : il est souvent utilisé pour les hacks laids.

eval (dans n’importe quelle langue) n’est pas mal comme une tronçonneuse n’est pas mal. C’est un outil. Il se trouve que c’est un outil puissant qui, lorsqu’il est mal utilisé, peut couper des membres et éviscérer (métaphoriquement), mais on peut dire la même chose pour de nombreux outils dans la boîte à outils d’un programmeur:

  • goto et amis
  • filetage à base de verrou
  • continuations
  • macros (hygiénique ou autre)
  • pointeurs
  • exceptions redémarrables
  • code auto-modifiable
  • … et des milliers de personnes.

Si vous devez utiliser l’un de ces outils puissants et potentiellement dangereux, demandez-vous trois fois pourquoi. dans une chaîne. Par exemple:

“Pourquoi dois-je utiliser eval ?” “A cause de foo.” “Pourquoi est-ce que c’est foo nécessaire?” “Car …”

Si vous parvenez à la fin de la chaîne et que l’outil semble toujours être la bonne chose à faire, alors faites-le. Documenter l’enfer hors de lui. Testez l’enfer hors de lui. Vérifiez l’exactitude et la sécurité encore et encore. Mais fais-le.

Eval c’est bien, pourvu que vous sachiez EXACTEMENT ce qui s’y passe. Toute entrée d’utilisateur y entrant DOIT être vérifiée et validée et tout. Si vous ne savez pas comment être sûr à 100%, alors ne le faites pas.

Fondamentalement, un utilisateur peut taper n’importe quel code pour la langue en question, et il s’exécutera. Vous pouvez imaginer pour vous combien de dégâts il peut faire.

“Quand dois-je utiliser eval ?” pourrait être une meilleure question.

La réponse courte est “lorsque votre programme est destiné à écrire un autre programme à l’exécution, puis l’exécuter”. La programmation génétique est un exemple de situation dans laquelle il est probablement logique d’utiliser eval .

IMO, cette question n’est pas spécifique au LISP . Voici une réponse à la même question pour PHP, et elle s’applique à LISP, Ruby et autres langages ayant un eval:

Les principaux problèmes avec eval () sont:

  • Entrée potentiellement dangereuse. Passer un paramètre non approuvé est un moyen d’échouer. Ce n’est souvent pas une tâche sortingviale de s’assurer qu’un paramètre (ou une partie de celui-ci) est totalement fiable.
  • Trickyess. Utiliser eval () rend le code intelligent, donc plus difficile à suivre. Pour citer Brian Kernighan “Le débogage est deux fois plus difficile que l’écriture du code. Par conséquent, si vous écrivez le code aussi intelligemment que possible, vous n’êtes pas assez intelligent pour le déboguer

Le principal problème avec l’utilisation réelle de eval () est un seul:

  • les développeurs inexpérimentés qui l’utilisent sans assez de considération.

Pris d’ ici

Je pense que la pièce est un point incroyable. L’obsession du code golf et du code concis a toujours abouti à un code “intelligent” (pour lequel les évaluations constituent un excellent outil). Mais vous devriez écrire votre code pour la lisibilité, IMO, non pas pour démontrer que vous êtes un malin et non pour économiser du papier (vous ne l’imprimerez pas de toute façon).

Ensuite, dans LISP, il y a un problème lié au contexte dans lequel eval est exécuté, donc le code non fiable peut avoir access à plus de choses. ce problème semble être commun de toute façon.

Il y a eu beaucoup de bonnes réponses mais voici un autre exemple de Matthew Flatt, l’un des exécutants de Racket:

http://blog.racket-lang.org/2011/10/on-eval-in-dynamic-languages-generally.html

Il fait beaucoup des points qui ont déjà été abordés mais certaines personnes trouveront peut-être que sa prise est intéressante.

Résumé: Le contexte dans lequel il est utilisé affecte le résultat d’eval mais n’est souvent pas pris en compte par les programmeurs, ce qui entraîne des résultats inattendus.

La réponse canonique est de restr à l’écart. Ce que je trouve bizarre, parce que c’est une primitive, et des sept primitives (les autres étant contre, voiture, cdr, if, eq et citation), ça prend de moins en moins de place et d’amour.

From On Lisp : “Habituellement, appeler explicitement eval, c’est comme acheter quelque chose dans un magasin de cadeaux à l’aéroport. Après avoir attendu le dernier moment, vous devez payer des prix élevés pour une sélection limitée de produits de second ordre.”

Alors, quand utiliser eval? Une utilisation normale est d’avoir une REPL dans votre REPL en évaluant (loop (print (eval (read)))) . Tout le monde va bien avec cette utilisation.

Mais vous pouvez également définir des fonctions en termes de macros qui seront évaluées après la compilation en combinant eval avec backquote. Tu vas

 (eval `(macro ,arg0 ,arg1 ,arg2)))) 

et ça va tuer le contexte pour vous.

Swank (pour emacs slime) est plein de ces cas. Ils ressemblent à ceci:

 (defun toggle-trace-aux (fspec &rest args) (cond ((member fspec (eval '(trace)) :test #'equal) (eval `(untrace ,fspec)) (format nil "~S is now untraced." fspec)) (t (eval `(trace ,@(if args `(:encapsulate nil) (list)) ,fspec ,@args)) (format nil "~S is now traced." fspec)))) 

Je ne pense pas que ce soit un piratage sale. Je l’utilise tout le temps moi-même pour réintégrer des macros dans des fonctions.

Un autre couple de points sur Lisp eval:

  • Il évalue sous l’environnement global, perdant votre contexte local.
  • Parfois, vous pouvez être tenté d’utiliser eval, alors que vous vouliez vraiment utiliser la macro de lecture ‘#.’ qui évalue au moment de la lecture.

Comme la “règle” de GOTO: Si vous ne savez pas ce que vous faites, vous pouvez faire un dégât.

Outre le fait de ne créer que des données connues et sûres, certains langages / implémentations ne peuvent pas optimiser le code. Vous pourriez vous retrouver avec du code interprété dans eval .

Eval n’est pas sûr. Par exemple, vous avez le code suivant:

 eval(' hello('.$_GET['user'].'); '); 

Maintenant, l’utilisateur accède à votre site et entre l’URL http://example.com/file.php?user= ); $ is_admin = true; echo (

Le code résultant serait alors:

 hello();$is_admin=true;echo(); 

Eval n’est pas mauvais. Eval n’est pas compliqué. C’est une fonction qui comstack la liste que vous lui transmettez. Dans la plupart des autres langages, comstackr du code arbitraire signifierait apprendre l’AST du langage et explorer les éléments internes du compilateur pour comprendre l’API du compilateur. En lisp, vous appelez simplement eval.

Quand devriez-vous l’utiliser? Chaque fois que vous avez besoin de comstackr quelque chose, généralement un programme qui accepte, génère ou modifie du code arbitraire à l’exécution .

Quand ne devrais-tu pas l’utiliser? Tous les autres cas.

Pourquoi ne devriez-vous pas l’utiliser quand vous n’en avez pas besoin? Parce que vous feriez quelque chose d’une manière inutilement compliquée qui pourrait causer des problèmes de lisibilité, de performances et de débogage.

Oui, mais si je suis débutant, comment puis-je savoir si je devrais l’utiliser? Toujours essayer de mettre en œuvre ce dont vous avez besoin avec des fonctions. Si cela ne fonctionne pas, ajoutez des macros. Si cela ne fonctionne toujours pas, alors eval!

Suivez ces règles et vous ne ferez jamais de mal avec eval 🙂

J’aime beaucoup la réponse de Zak et il a compris l’essentiel: eval est utilisé lorsque vous écrivez une nouvelle langue, un script ou une modification d’une langue. Il n’explique pas vraiment plus, alors je vais donner un exemple:

 (eval (read-line)) 

Dans ce programme Lisp simple, l’utilisateur est invité à entrer et tout ce qu’il saisit est évalué. Pour que cela fonctionne, l’ensemble des définitions de symboles doit être présent si le programme est compilé, car vous n’avez aucune idée des fonctions que l’utilisateur pourrait entrer, vous devez donc les inclure toutes. Cela signifie que si vous comstackz ce programme simple, le binary résultant sera gigantesque.

Par principe, vous ne pouvez même pas considérer cela comme une déclaration compilable pour cette raison. En général, une fois que vous utilisez eval , vous travaillez dans un environnement interprété et le code ne peut plus être compilé. Si vous n’utilisez pas eval, vous pouvez comstackr un programme Lisp ou Scheme comme un programme C. Par conséquent, vous voulez vous assurer que vous voulez et devez être dans un environnement interprété avant de vous engager à utiliser eval .