Почему именно это зло?

Я знаю, что программисты Lisp и Scheme обычно говорят, что eval следует избегать, если это строго необходимо. Я видел ту же рекомендацию для нескольких языков программирования, но Ive еще не видел список четких аргументов против использования eval. Где я могу найти отчет о потенциальных проблемах использования eval?

Например, я знаю проблемы GOTO в процедурном программировании (делает программы нечитабельными и трудноподдерживаемыми, затрудняет поиск проблем безопасности и т.д.), но я никогда не видел аргументов против eval.

Интересно, что те же аргументы против GOTO должны быть действительными в отношении продолжений, но я вижу, что Schemers, например, не скажут, что продолжения являются "злыми" - вы должны просто быть осторожными при их использовании. Theyre гораздо более склонны хмуриться на код, используя eval, чем на код с использованием продолжений (насколько я вижу - я мог ошибаться).

Ответы

Ответ 1

Существует несколько причин, по которым нельзя использовать EVAL.

Основная причина для начинающих: вам это не нужно.

Пример (при условии, что Common Lisp):

EVALuate выражение с различными операторами:

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

Это лучше написано как:

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

Есть много примеров, когда начинающие ученики Lisp думают, что им нужно EVAL, но они им не нужны - поскольку выражения оцениваются, а также можно оценить часть функции. В большинстве случаев использование EVAL показывает отсутствие понимания оценщика.

Это та же проблема с макросами. Часто начинающие пишут макросы, где они должны писать функции - не понимая, какие макросы действительно нужны и не понимают, что функция уже выполняет эту работу.

Часто неправильным инструментом для работы является EVAL, и это часто указывает, что новичок не понимает обычные правила оценки Lisp.

Если вам кажется, что вам нужно EVAL, тогда проверьте, можно ли вместо этого использовать FUNCALL, REDUCE или APPLY.

  • FUNCALL - вызов функции с аргументами: (funcall '+ 1 2 3)
  • REDUCE - вызвать функцию в списке значений и объединить результаты: (reduce '+ '(1 2 3))
  • APPLY - вызовите функцию со списком в качестве аргументов: (apply '+ '(1 2 3)).

Q: мне действительно нужен eval или уже компилятор/оценщик, что я действительно хочу?

Основные причины, чтобы избежать EVAL для немного более продвинутых пользователей:

  • вы хотите убедиться, что ваш код скомпилирован, потому что компилятор может проверить код для многих проблем и генерировать более быстрый код, иногда MUCH MUCH MUCH (этот коэффициент 1000;-)) более быстрый код

  • код, который был сконструирован и нуждается в оценке, не может быть скомпилирован как можно раньше.

  • eval произвольного пользовательского ввода открывает проблемы безопасности

  • некоторое использование оценки с помощью EVAL может произойти в неподходящее время и создать проблемы с построением

Чтобы пояснить последнюю точку с упрощенным примером:

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

Итак, я могу написать макрос, который на основе первого параметра использует либо SIN, либо COS.

(foo 3 4) делает (sin 4) и (foo 1 4) делает (cos 4).

Теперь мы можем иметь:

(foo (+ 2 1) 4)

Это не дает желаемого результата.

Затем может потребоваться восстановление макроса FOO путем EVALuating переменной:

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

(foo (+ 2 1) 4)

Но это все равно не работает:

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

Значение переменной неизвестно во время компиляции.

Общей важной причиной, чтобы избежать EVAL:, часто используется для уродливых хаков.

Ответ 2

eval (на любом языке) не является злом так же, как бензопила не является злом. Это инструмент. Это, оказывается, мощный инструмент, который при неправильном использовании может разрывать конечности и потрошить (метафорически), но то же самое можно сказать и о многих инструментах в программном наборе программистов, включая:

  • goto и друзья
  • потоковая обработка на основе блокировки
  • продолжениями
  • макросы (гигиенические или другие)
  • Указатели
  • перезапускаемые исключения
  • самомодифицирующийся код
  • ... и количество тысяч.

Если вам нужно использовать любой из этих мощных, потенциально опасных инструментов, спросите себя три раза "почему?". в цепочке. Например:

"Зачем мне использовать eval?" "Из-за foo". "Почему foo необходимо?" "Потому что..."

Если вы дойдете до конца этой цепи, и инструмент по-прежнему выглядит так, как надо, сделайте это. Извлеките Ад. Испытайте Ад из этого. Проверяйте правильность и безопасность дважды и снова. Но сделайте это.

Ответ 3

Eval в порядке, если вы знаете ТОЧНО, что происходит в нем. Любой входящий в него вход пользователя ДОЛЖЕН быть проверен и проверен и все. Если вы не знаете, как быть на 100% уверенным, тогда не делайте этого.

В принципе, пользователь может ввести любой код для соответствующего языка и выполнить его. Вы можете себе представить, сколько урона он может сделать.

Ответ 4

"Когда мне следует использовать eval?" может быть лучшим вопросом.

Короткий ответ: "когда ваша программа предназначена для записи другой программы во время выполнения, а затем ее запуска". Генетическое программирование является примером ситуации, когда, вероятно, имеет смысл использовать eval.

Ответ 5

IMO, этот вопрос не относится к LISP. Вот ответ на тот же вопрос для PHP, и он относится к LISP, Ruby и другому языку, который имеет eval:

Основные проблемы с eval():

  • Потенциальный небезопасный ввод. Передача ненадежного параметра - это способ потерпеть неудачу. Это часто не тривиальная задача чтобы убедиться, что параметр (или часть от этого) полностью доверяют.
  • Trickyness. Использование eval() делает код умным, поэтому сложнее следить. Процитировать Брайана Кернигана "Отладка в два раза сложнее, чем написав код в первую очередь. Поэтому, если вы пишете код как насколько это возможно, вы, по определение, недостаточно умное для отладки это"

Основная проблема с фактическим использованием eval() является только одним:

  • неопытные разработчики, которые используют его без достаточного рассмотрения.

Взято из здесь.

Я думаю, что эта штука хитрости - удивительный момент. Одержимость кодовым гольф-полем и сжатым кодом всегда приводила к "умному" коду (для которого evals - отличный инструмент). Но вы должны написать свой код для удобочитаемости, IMO, а не для того, чтобы продемонстрировать, что вы умный и не для экономии бумаги (вы все равно не будете его печатать).

Тогда в LISP существует некоторая проблема, связанная с контекстом, в котором выполняется eval, поэтому ненадежный код может получить доступ к большему количеству вещей; эта проблема, кажется, является общей в любом случае.

Ответ 6

Было много отличных ответов, но вот еще одно взятие от Мэтью Флатта, одного из разработчиков Racket:

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

Он делает много очков, которые уже были покрыты, но некоторые люди могут найти его интересным тем не менее.

Резюме: контекст, в котором он используется, влияет на результат eval, но часто не рассматривается программистами, что приводит к неожиданным результатам.

Ответ 7

Канонический ответ - держаться подальше. Который я нахожу странным, потому что он примитивен и из семи примитивов (другие - минусы, автомобиль, cdr, if, eq и quote), он получает далеко и меньше наименьшее количество пользы и любви.

From On Lisp: "Как правило, вызов eval явно похож на покупку чего-то в сувенирном магазине аэропорта. Подождав до последнего момента, вы должны заплатить высокие цены за ограниченный выбор второсортных товаров".

Итак, когда я использую eval? Одно нормальное использование - иметь REPL в вашем REPL, оценивая (loop (print (eval (read)))). Все хорошо с этим использованием.

Но вы также можете определить функции в терминах макросов, которые будут оцениваться после компиляции путем объединения eval с backquote. Вы идете

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

и он убьет контекст для вас.

Swank (для emacs slime) заполнен этими случаями. Они выглядят так:

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

Я не думаю, что это грязный хак. Я все время использую его для реинтеграции макросов в функции.

Ответ 8

Еще одна пара точек на Lisp eval:

  • Он оценивается в глобальной среде, теряя локальный контекст.
  • Иногда может возникнуть соблазн использовать eval, когда вы действительно хотите использовать read-macro '#.' который оценивается во время чтения.

Ответ 9

Как правило GOTO: если вы не знаете, что делаете, вы можете сделать беспорядок.

Помимо создания только чего-то из известных и безопасных данных, возникает проблема, что некоторые языки/реализации не могут оптимизировать код достаточно. Вы можете получить интерпретируемый код внутри eval.

Ответ 10

Eval просто небезопасен. Например, у вас есть следующий код:

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

Теперь пользователь приходит на ваш сайт и вводит url http://example.com/file.php?user=); $is_admin = true; echo (

Тогда получившийся код будет выглядеть следующим образом:

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

Ответ 11

Эваль не злой. Eval не сложно. Это функция, которая компилирует список, который вы передаете ему. В большинстве других языков компиляция произвольного кода означала бы изучение языка АСТ и поиск во внутренних компонентах компилятора для определения API компилятора. В lisp вы просто вызываете eval.

Когда вы его используете? Всякий раз, когда вам нужно что-то компилировать, обычно программа, которая принимает, генерирует или изменяет произвольный код во время выполнения.

Когда вы не используете его? Все остальные случаи.

Почему бы вам не использовать его, когда вам это не нужно? Потому что вы делаете что-то излишне сложным способом, что может вызвать проблемы для удобочитаемости, производительности и отладки.

Да, но если я новичок, как я знаю, буду ли я использовать его? Всегда старайтесь выполнять то, что вам нужно, с функциями. Если это не работает, добавьте макросы. Если это все еще не работает, тогда eval!

Следуйте этим правилам, и вы никогда не будете делать зло с помощью eval:)

Ответ 12

Мне нравится Zak ответить, и он понял суть вопроса: eval используется, когда вы пишете новый язык, script, или изменение языка. Он не объясняет дальше, поэтому приведу пример:

(eval (read-line))

В этой простой программе Lisp пользователю предлагается ввести ввод, а затем все, что они вводят, оценивается. Чтобы это работало, весь набор определений символов должен присутствовать, если программа скомпилирована, потому что вы не знаете, какие функции пользователь может ввести, поэтому вы должны включить их все. Это означает, что если вы скомпилируете эту простую программу, результирующий двоичный файл будет гигантским.

В принципе, вы даже не можете считать это компилятивным утверждением по этой причине. В общем, как только вы используете eval, вы работаете в интерпретируемой среде, и код больше не может быть скомпилирован. Если вы не используете eval, тогда вы можете скомпилировать программу Lisp или Scheme, как программу C. Поэтому вы хотите убедиться, что хотите и должны находиться в интерпретируемой среде, прежде чем совершать использование eval.