[clojure] 왜 평가가 정확히 악한가?

나는 Lisp와 Scheme 프로그래머들이 일반적으로 eval꼭 필요한 경우가 아니라면 피해야 한다고 말합니다 . 여러 프로그래밍 언어에 대해 동일한 권장 사항을 보았지만에 대한 사용에 대한 명확한 주장 목록을 아직 보지 못했습니다 eval. 사용의 잠재적 문제에 대한 설명은 어디서 찾을 수 있습니까 eval?

예를 들어, GOTO절차 적 프로그래밍 의 문제 (프로그램을 읽을 수없고 유지하기 어렵게 만들고 보안 문제를 찾기 어렵게 만드는 등)를 알고 있지만에 대한 주장을 본 적이 없습니다 eval.

흥미롭게도, 같은 주장에 GOTO대해서는 계속해서 반대 해야하지만 Schemers는 예를 들어, 계속이 “악”이라고 말하지 않을 것입니다. 사용시주의해야합니다. eval연속체를 사용하는 코드보다 코드를 사용하여 눈살을 찌푸 릴 가능성이 훨씬 높습니다 (내가 볼 수있는 한 잘못 될 수 있습니다).



답변

사용하지 말아야 할 몇 가지 이유가 있습니다 EVAL.

초보자의 주된 이유는 필요하지 않기 때문입니다.

예 (공통 리스프 가정) :

다른 연산자를 사용하여 표현식을 평가하십시오.

(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약간 더 고급 사용자 를 피해야하는 주요 이유 :

  • 컴파일러가 많은 문제에 대한 코드를 검사하고 더 빠른 코드를 생성 할 수 있기 때문에 코드가 컴파일되도록하려는 경우가 있습니다.

  • 구성되고 평가되어야하는 코드는 가능한 빨리 컴파일 할 수 없습니다.

  • 임의의 사용자 입력 평가로 보안 문제가 발생 함

  • 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(어떤 언어로든) 전기 톱이 악한 것과 같은 방식으로 악한 것은 아닙니다. 도구입니다. 잘못 사용하면 팔다리를 절단하고 (비 유적으로 말하면) 볼 수있는 강력한 도구이지만, 프로그래머의 도구 상자에있는 많은 도구에 대해서도 다음과 같이 말할 수 있습니다.

  • goto 그리고 친구들
  • 잠금 기반 스레딩
  • 연속
  • 매크로 (유전자 또는 기타)
  • 포인터
  • 재시작 가능한 예외
  • 자체 수정 코드
  • … 그리고 수천의 캐스트.

이러한 강력하고 잠재적으로 위험한 도구를 사용해야하는 경우 “왜?” 사슬에서. 예를 들면 다음과 같습니다.

“왜 사용해야합니까 eval 합니까?” “foo 때문에.” “왜 foo가 필요한가?” “때문에 …”

해당 체인의 끝에 도달했지만 도구가 여전히 옳은 것처럼 보이는 경우 수행하십시오. 지옥을 문서화하십시오. 지옥을 시험해보십시오. 반복해서 정확성과 보안을 다시 확인하십시오. 하지만하세요.


답변

정확히 아는 한 평가는 괜찮습니다.무슨 일이 일어나고 있다면 습니다. 여기에 들어가는 모든 사용자 입력을 확인하고 확인해야합니다. 100 % 확신하는 방법을 모른다면 그렇게하지 마십시오.

기본적으로 사용자는 해당 언어의 코드를 입력 할 수 있으며 실행됩니다. 그가 할 수있는 피해량을 스스로 상상할 수 있습니다.


답변

“언제 사용해야 eval합니까?” 더 좋은 질문이 될 수 있습니다.

짧은 대답은 “프로그램이 런타임에 다른 프로그램을 작성하고 실행하려고 할 때”입니다. 유전자 프로그래밍 은 사용하기에 적합한 상황의 예입니다 eval.


답변

IMO, 이 질문은 LISP에만 국한되지 않습니다 . 다음은 PHP와 동일한 질문에 대한 답변이며 LISP, Ruby 및 기타 평가 기능이있는 다른 언어에 적용됩니다.

eval ()의 주요 문제점은 다음과 같습니다.

  • 잠재적으로 안전하지 않은 입력. 신뢰할 수없는 매개 변수를 전달하면 실패 할 수 있습니다. 매개 변수 (또는 그 일부)를 완전히 신뢰하는 것은 쉬운 일이 아닙니다.
  • 어리 석음. eval ()을 사용하면 코드가 영리 해지기 때문에 따르기가 더 어렵습니다. Brian Kernighan의 말을 인용하자면, ” 디버깅은 처음에 코드를 작성하는 것보다 두 배나 어렵습니다. 따라서 가능한 한 영리하게 코드를 작성한다면, 정의에 따르면 디버그하기에 충분히 똑똑하지 않습니다

실제로 eval ()을 사용할 때의 주요 문제는 하나뿐입니다.

  • 충분히 고려하지 않고 사용하는 경험이없는 개발자.

여기 에서 찍은 .

나는 까다로운 부분이 놀라운 포인트라고 생각합니다. 코드 골프와 간결한 코드에 대한 집착은 항상 “영리한”코드를 만들어 냈습니다 (에바는 훌륭한 도구입니다). 그러나 가독성을 높이고 종이를 절약하지 않음을 나타 내기 위해 가독성을위한 코드를 작성해야합니다. (당신이 어쨌든 인쇄되지 않습니다).

그런 다음 LISP에는 eval이 실행되는 컨텍스트와 관련된 몇 가지 문제가 있으므로 신뢰할 수없는 코드는 더 많은 것에 액세스 할 수 있습니다. 이 문제는 어쨌든 흔한 것 같습니다.


답변

많은 훌륭한 답변이 있었지만 여기에 라켓의 구현 자 중 하나 인 Matthew Flatt가 가져 왔습니다.

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

그는 이미 다룬 많은 요점을 제시하지만 일부 사람들은 그럼에도 불구하고 그의 관심을 끌 수 있습니다.

요약 : 사용 된 컨텍스트는 평가 결과에 영향을 주지만 종종 프로그래머가 고려하지 않아 예기치 않은 결과가 발생합니다.


답변

정식 답변은 멀리하는 것입니다. 나는 그것이 원시적이기 때문에 이상한 것을 발견하고, 7 가지 기본 요소 (다른 것들은 죄수, 자동차, cdr, if, eq 및 quote) 중 가장 적은 양의 사용과 사랑을 얻습니다.

에서 에 리스프 “. 일반적으로, 명시 적으로 평가를 호출하면 공항 선물 가게에서 뭔가를 구입처럼 마지막 순간까지 기다렸다 데, 당신은 이류 제품의 제한된 선택을 위해 높은 가격을 지불해야한다”고 말했다.

eval은 언제 사용합니까? 일반적인 사용 중 하나는를 평가하여 REPL 내에 REPL을 갖는 것 (loop (print (eval (read))))입니다. 그 사용에는 누구나 괜찮습니다.

그러나 eval과 backquote를 결합하여 컴파일 평가 될 매크로로 함수를 정의 할 수도 있습니다 . 너가

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

그리고 그것은 당신을 위해 문맥을 죽일 것입니다.

스 nk 크 (이맥스 점액의 경우)에는 이러한 경우가 가득합니다. 그들은 다음과 같이 보입니다 :

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

나는 그것이 더러운 해킹이라고 생각하지 않습니다. 매크로를 함수로 다시 통합하기 위해 항상 사용합니다.