Я знаю, что программисты Lisp и Scheme обычно говорят, что этого eval
следует избегать, за исключением случаев крайней необходимости. Я видел ту же рекомендацию для нескольких языков программирования, но еще не видел списка четких аргументов против использования eval
. Где я могу найти отчет о потенциальных проблемах использования eval
?
Например, я знаю проблемы GOTO
процедурного программирования (делает программы нечитаемыми и трудными в обслуживании, затрудняет поиск проблем безопасности и т. Д.), Но я никогда не видел аргументов против eval
.
Интересно, что те же аргументы против GOTO
должны быть действительны и против продолжений, но я вижу, что Schemers, например, не скажет, что продолжения - это «зло» - вы должны просто быть осторожны при их использовании. Они с гораздо большей вероятностью будут недовольны использованием кода, eval
чем кодом, использующим продолжения (насколько я понимаю, я могу ошибаться).
goto
"зло", потому что это форма мутации: фактически новое значение внезапно присваивается указателю инструкции. Продолжение не предполагает мутаций; чисто функциональный язык может иметь продолжения. Они более чистые, чем управляющие конструкции вроде if и while , которые Дейкстра устраивал, несмотря на то , что они были легким синтаксическим сахаром по сравнению с goto и метками.Ответы:
Есть несколько причин, по которым нельзя использовать
EVAL
.Основная причина для новичков: вам это не нужно.
Пример (при условии Common Lisp):
Оцените выражение с помощью разных операторов:
(let ((ops '(+ *))) (dolist (op ops) (print (eval (list op 1 2 3)))))
Лучше написать так:
(let ((ops '(+ *))) (dolist (op ops) (print (funcall op 1 2 3))))
Существует множество примеров, когда новички, изучающие Лисп, думают, что им это нужно
EVAL
, но им это не нужно - поскольку выражения вычисляются, и можно также оценить функциональную часть. В большинстве случаев использованиеEVAL
показывает отсутствие понимания оценщика.Та же проблема с макросами. Часто новички пишут макросы там, где им следует писать функции - не понимая, для чего на самом деле макросы, и не понимая, что функция уже выполняет свою работу.
Часто это неподходящий инструмент для работы,
EVAL
и он часто указывает на то, что новичок не понимает обычных правил оценки Lisp.Если вы думаете , что нужно
EVAL
, а затем проверить , если что - то подобноеFUNCALL
,REDUCE
илиAPPLY
может быть использован вместо.FUNCALL
- вызвать функцию с аргументами:(funcall '+ 1 2 3)
REDUCE
- вызвать функцию из списка значений и объединить результаты:(reduce '+ '(1 2 3))
APPLY
- вызов функции со списком в качестве аргументов:(apply '+ '(1 2 3))
.В: действительно ли мне нужен eval или компилятор / вычислитель уже делает то, что мне действительно нужно?
Основные причины, которых следует избегать
EVAL
для немного более продвинутых пользователей:вы хотите убедиться, что ваш код скомпилирован, потому что компилятор может проверять код на наличие многих проблем и генерирует более быстрый код, иногда НАМНОГО НАМНОГО (это фактор 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
, оценив переменную:(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
: он часто используется для уродливых взломов.источник
eval
просто потому, что не знают, что есть конкретный язык или функция библиотеки, чтобы делать то, что они хотят делать. Аналогичный пример из JS: я хочу получить свойство от объекта, используя динамическое имя, поэтому я пишу:eval("obj.+" + propName)
когда я мог бы написатьobj[propName]
."obj.+"
? Последнее, что я проверял,+
недействительно при использовании точечных ссылок в JS.eval
(на любом языке) не является злом, как бензопила не является злом. Это инструмент. Это мощный инструмент, который при неправильном использовании может отрезать конечности и выпотрошить (образно говоря), но то же самое можно сказать и о многих инструментах в наборе инструментов программиста, включая:goto
и друзьяЕсли вам приходится использовать любой из этих мощных, потенциально опасных инструментов, трижды спросите себя: «Почему?» в цепочке. Например:
Если вы дойдете до конца этой цепочки, а инструмент по-прежнему выглядит правильным, сделайте это. Задокументируйте ад из него. Испытайте ад из него. Перепроверяйте правильность и безопасность снова и снова. Но сделай это.
источник
Eval прекрасен, если вы ТОЧНО знаете, что в него входит. Любой ввод пользователя в него ДОЛЖЕН быть проверен и подтвержден, и все такое. Если вы не знаете, как быть уверенным на 100%, не делайте этого.
По сути, пользователь может ввести любой код для рассматриваемого языка, и он будет выполнен. Вы можете себе представить, какой ущерб он может нанести.
источник
"Когда я должен использовать
eval
?" может быть лучшим вопросом.Короткий ответ: «когда ваша программа предназначена для написания другой программы во время выполнения, а затем для ее выполнения». Генетическое программирование - это пример ситуации, в которой его, вероятно, имеет смысл использовать
eval
.источник
ИМО, этот вопрос не относится к LISP . Вот ответ на тот же вопрос для PHP, и он применим к LISP, Ruby и другим другим языкам, в которых есть eval:
Взято отсюда .
Я думаю, что хитрость - это замечательный момент. Одержимость кодом для гольфа и кратким кодом всегда приводила к «умному» коду (для которого evals - отличный инструмент). Но вы должны писать свой код для удобства чтения, IMO, а не для демонстрации того, что вы сообразительный, и не для экономии бумаги (вы все равно не будете его печатать).
Затем в LISP возникает некоторая проблема, связанная с контекстом, в котором выполняется eval, поэтому ненадежный код может получить доступ к большему количеству вещей; в любом случае эта проблема кажется обычной.
источник
Было много отличных ответов, но вот еще один пример Мэтью Флэтта, одного из разработчиков Racket:
http://blog.racket-lang.org/2011/10/on-eval-in-dynamic-languages-generally.html
Он подчеркивает многие моменты, которые уже были рассмотрены, но, тем не менее, некоторые люди могут найти его точку зрения интересной.
Описание: Контекст, в котором он используется, влияет на результат eval, но часто не учитывается программистами, что приводит к неожиданным результатам.
источник
Канонический ответ - держаться подальше. Что я нахожу странным, потому что это примитив, и из семи примитивов (остальные - cons, car, cdr, if, eq и quote) он получает гораздо меньше пользы и любви.
From On Lisp : «Обычно явный вызов eval - это как покупка чего-то в сувенирном магазине в аэропорту. Выждав до последнего момента, вы должны заплатить высокую цену за ограниченный выбор второсортных товаров».
Итак, когда мне использовать eval? Обычное использование - иметь REPL в вашем REPL путем оценки
(loop (print (eval (read))))
. Всем нравится такое использование.Но вы также можете определять функции в терминах макросов, которые будут оцениваться после компиляции, комбинируя eval с обратной кавычкой. Ты иди
(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))))
Не думаю, что это мерзкий взлом. Я сам постоянно использую его для реинтеграции макросов в функции.
источник
Еще пара замечаний по оценке Lisp:
источник
Как "правило" GOTO: если вы не знаете, что делаете, вы можете навести беспорядок.
Помимо создания чего-то только из известных и безопасных данных, существует проблема, заключающаяся в том, что некоторые языки / реализации не могут достаточно оптимизировать код. Вы можете получить интерпретируемый код внутри
eval
.источник
Eval просто небезопасен. Например, у вас есть следующий код:
eval(' hello('.$_GET['user'].'); ');
Теперь пользователь заходит на ваш сайт и вводит url http://example.com/file.php?user= ); $ is_admin = true; echo (
Тогда результирующий код будет:
hello();$is_admin=true;echo();
источник
eval
любом языке, в котором он есть.Эвал не зло. Оценка не сложная. Это функция, которая составляет список, который вы ей передаете. В большинстве других языков компиляция произвольного кода означала бы изучение AST языка и копание во внутренностях компилятора, чтобы выяснить API компилятора. В lisp вы просто вызываете eval.
Когда его использовать? Всякий раз, когда вам нужно что-то скомпилировать, обычно это программа, которая принимает, генерирует или изменяет произвольный код во время выполнения .
Когда нельзя его использовать? Все остальные случаи.
Почему бы вам не использовать его, когда в этом нет необходимости? Потому что вы будете делать что-то излишне сложным способом, что может вызвать проблемы с читабельностью, производительностью и отладкой.
Да, но если я новичок, как мне узнать, стоит ли мне его использовать? Всегда старайтесь реализовать то, что вам нужно, с помощью функций. Если это не сработает, добавьте макросы. Если это все еще не работает, то eval!
Следуйте этим правилам, и вы никогда не сделаете зла с eval :)
источник
Мне очень нравится ответ Зака, и он понял суть дела: eval используется, когда вы пишете новый язык, сценарий или модифицируете язык. Он не объясняет подробностей, поэтому я приведу пример:
(eval (read-line))
В этой простой программе на Лиспе пользователю предлагается ввести данные, а затем все, что он вводит, оценивается. Чтобы это работало, при компиляции программы должен присутствовать весь набор определений символов, поскольку вы не знаете, какие функции может вводить пользователь, поэтому вы должны включить их все. Это означает, что если вы скомпилируете эту простую программу, полученный двоичный файл будет гигантским.
По этой причине вы, в принципе, не можете даже считать это заявление компилируемым. В общем, как только вы используете eval , вы работаете в интерпретируемой среде, и код больше не может быть скомпилирован. Если вы не используете eval, вы можете скомпилировать программу на Lisp или Scheme так же, как программу на C. Следовательно, вы хотите убедиться, что хотите и должны находиться в интерпретируемой среде, прежде чем переходить к использованию eval .
источник