clojure {} - ¿Por qué exactamente eval evalúa mal?




mapa reduce (11)

Sé que los programadores de Lisp y Scheme generalmente dicen que se debe evitar la eval menos que sea estrictamente necesario. He visto la misma recomendación para varios lenguajes de programación, pero aún no he visto una lista de argumentos claros contra el uso de eval . ¿Dónde puedo encontrar una cuenta de los problemas potenciales del uso de eval ?

Por ejemplo, conozco los problemas de GOTO en la programación de procedimientos (hace que los programas sean ilegibles y difíciles de mantener, hace que los problemas de seguridad sean difíciles de encontrar, etc.), pero nunca he visto los argumentos en contra de la eval .

Curiosamente, los mismos argumentos en contra de GOTO deberían ser válidos contra las continuaciones, pero veo que Schemers, por ejemplo, no dirá que las continuidades son "malas"; solo debe tener cuidado al usarlas. Son mucho más propensos a fruncir el ceño sobre el código usando eval que sobre el código usando continuaciones (por lo que puedo ver, podría estar equivocado).


Answers

Hay varias razones por las cuales uno no debería usar EVAL .

La razón principal para los principiantes es: no lo necesitas.

Ejemplo (suponiendo Common Lisp):

EVALua una expresión con diferentes operadores:

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

Eso está mejor escrito como:

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

Hay muchos ejemplos donde los principiantes que están aprendiendo Lisp creen que necesitan EVAL , pero no lo necesitan, ya que las expresiones se evalúan y también se puede evaluar la parte de la función. La mayoría de las veces, el uso de EVAL muestra una falta de comprensión del evaluador.

Es el mismo problema con las macros. A menudo los principiantes escriben macros, donde deben escribir funciones, sin entender para qué son realmente las macros y sin comprender que una función ya hace el trabajo.

A menudo es la herramienta incorrecta para el trabajo usar EVAL y a menudo indica que el principiante no comprende las reglas de evaluación de Lisp habituales.

Si crees que necesitas EVAL , entonces verifica si se puede usar algo como FUNCALL , REDUCE o APPLY .

  • FUNCALL - llama a una función con argumentos: (funcall '+ 1 2 3)
  • REDUCE : llama a una función en una lista de valores y combina los resultados: (reduce '+ '(1 2 3))
  • APPLY - llama a una función con una lista como argumentos: (apply '+ '(1 2 3)) .

P: ¿Realmente necesito evaluar o el compilador / evaluador ya es lo que realmente quiero?

Las principales razones para evitar EVAL para usuarios un poco más avanzados:

  • usted quiere asegurarse de que su código esté compilado, porque el compilador puede verificar el código para muchos problemas y genera un código más rápido, a veces MUCHO MUCHO (ese es el factor 1000 ;-)) código más rápido

  • el código que se construye y necesita ser evaluado no se puede compilar tan pronto como sea posible.

  • La evaluación de la entrada de usuario arbitraria abre los problemas de seguridad

  • algunos usos de la evaluación con EVAL pueden ocurrir en el momento equivocado y crear problemas de construcción

Para explicar el último punto con un ejemplo simplificado:

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

Por lo tanto, es posible que desee escribir una macro que, según el primer parámetro, use SIN o COS .

(foo 3 4) does (sin 4) y (foo 1 4) does (cos 4) .

Ahora podemos tener:

(foo (+ 2 1) 4)

Esto no da el resultado deseado.

Uno puede querer reparar el macro FOO al EVALUAR la variable:

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

(foo (+ 2 1) 4)

Pero luego, esto aún no funciona:

(defun bar (a b)
  (foo a b))

El valor de la variable simplemente no se conoce en el momento de la compilación.

Una razón general importante para evitar EVAL : a menudo se usa para hacks feos.


Eval está bien, siempre y cuando se sepa EXACTAMENTE qué es lo que entra. Cualquier entrada de usuario que ingrese DEBE ser verificada y validada, y todo. Si no sabes cómo estar 100% seguro, entonces no lo hagas.

Básicamente, un usuario puede escribir cualquier código para el idioma en cuestión y se ejecutará. Puedes imaginarte cuánto daño puede hacer.


Ha habido muchas respuestas excelentes, pero esta es otra toma de Matthew Flatt, uno de los implementadores de Racket:

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

Él hace muchos de los puntos que ya se han cubierto, pero algunas personas pueden encontrar su toma interesante, no obstante.

Resumen: el contexto en el que se utiliza afecta el resultado de la evaluación, pero a menudo no es considerado por los programadores, lo que lleva a resultados inesperados.


Eval no es malvado Eval no es complicado. Es una función que compila la lista que le pasa. En la mayoría de los otros lenguajes, la compilación de código arbitrario significaría aprender el AST del idioma y explorar en el interior del compilador para descubrir la API del compilador. En lisp, simplemente llama a eval.

¿Cuándo deberías usarlo? Siempre que necesite compilar algo, generalmente un programa que acepta, genera o modifica código arbitrario en tiempo de ejecución .

¿Cuándo no deberías usarlo? Todos los demás casos.

¿Por qué no deberías usarlo cuando no es necesario? Porque estarías haciendo algo innecesariamente complicado que puede causar problemas de legibilidad, rendimiento y depuración.

Sí, pero si soy un principiante, ¿cómo sé si debería usarlo? Siempre trate de implementar lo que necesita con las funciones. Si eso no funciona, agregue macros. Si eso todavía no funciona, entonces eval!

Sigue estas reglas y nunca harás mal con eval :)


Como la "regla" de GOTO: si no sabes lo que estás haciendo, puedes hacer un desastre.

Además de solo crear algo a partir de datos conocidos y seguros, existe el problema de que algunos lenguajes / implementaciones no pueden optimizar el código lo suficiente. Podría terminar con un código interpretado dentro de eval .


Otro par de puntos en Lisp eval:

  • Evalúa bajo el entorno global, perdiendo su contexto local.
  • A veces puede estar tentado de usar eval, cuando realmente quería usar el read-macro '#'. que se evalúa en tiempo de lectura.

La respuesta canónica es mantenerse alejado. Lo cual me parece raro, porque es un primitivo, y de los siete primitivos (los otros son cons, coche, cdr, if, eq y quote), obtiene de lejos la menor cantidad de uso y amor.

De On Lisp : "Generalmente, llamar a eval explícitamente es como comprar algo en una tienda de regalos del aeropuerto. Habiendo esperado hasta el último momento, tienes que pagar precios altos por una selección limitada de productos de segunda categoría".

Entonces, ¿cuándo uso eval? Un uso normal es tener un REPL dentro de su REPL mediante la evaluación (loop (print (eval (read)))) . Todos están bien con ese uso.

Pero también puede definir funciones en términos de macros que se evaluarán después de la compilación combinando eval con backquote. Anda tu

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

y matará el contexto para ti.

Swank (para baba de emacs) está lleno de estos casos. Se ven así:

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

No creo que sea un hack sucio. Lo uso todo el tiempo para reinsertar macros en funciones.


Me gusta mucho la respuesta de Zak y él ha captado la esencia del asunto: eval se usa cuando estás escribiendo un nuevo idioma, un guión o modificación de un idioma. Él realmente no explica más, así que daré un ejemplo:

(eval (read-line))

En este sencillo programa Lisp, al usuario se le solicita una entrada y luego se evalúa todo lo que ingresa. Para que esto funcione, el conjunto completo de definiciones de símbolos debe estar presente si el programa está compilado, porque no tiene idea de qué funciones puede ingresar el usuario, por lo que debe incluirlas todas. Eso significa que si compila este sencillo programa, el binario resultante será gigantesco.

Como cuestión de principio, ni siquiera puede considerar esto como una declaración compilable por este motivo. En general, una vez que usa eval , está operando en un entorno interpretado y el código ya no se puede compilar. Si no utiliza eval, entonces puede compilar un programa Lisp o Scheme como un programa C. Por lo tanto, desea asegurarse de que desea y necesita estar en un entorno interpretado antes de comprometerse a usar eval .


Eval es simplemente inseguro. Por ejemplo, tiene el siguiente código:

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

Ahora el usuario accede a su sitio y ingresa url http://example.com/file.php?user= ); $ is_admin = true; echo (

Entonces el código resultante sería:

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

"¿Cuándo debería usar eval ?" podría ser una mejor pregunta.

La respuesta breve es "cuando su programa está destinado a escribir otro programa en tiempo de ejecución y luego ejecutarlo". La programación genética es un ejemplo de una situación en la que probablemente tenga sentido usar eval .


Las palabras clave son símbolos que se evalúan a sí mismos, por lo que no tiene que recordar citarlos.





clojure scheme lisp common-lisp eval