2017-04-03 10 views
2

Ch 8 끝 부분 인 Practical Common Lisp에서 Peter Seibel은 once-only 매크로를 제공합니다. 이 기능의 목적은 사용자 정의 매크로에서 다양한 평가를 통해 여러 가지 미묘한 문제를 완화하는 것입니다.`once-only` 매크로 사용

(defmacro once-only ((&rest names) &body body) 
    (let ((gensyms (loop for n in names collect (gensym)))) 
    `(let (,@(loop for g in gensyms collect `(,g (gensym)))) 
     `(let (,,@(loop for g in gensyms for n in names collect ``(,,g ,,n))) 
     ,(let (,@(loop for n in names for g in gensyms collect `(,n ,g))) 
      ,@body))))) 

다음에 시도 샘플 (잘못된) 인위적인 매크로는 : 난 그냥 그것을 사용하는 방법을 제대로이 매크로 다른 게시물에서와 작품,하지만 방법이 시점에서 이해하려고 노력하고 있지 않다 참고 다양한 변수 평가 문제를 나타냅니다. 이 범위를 반환, 일부 델타에 의해 정수의 범위를 반복하는 취지 :

(defmacro do-range ((var start stop delta) &body body) 
    "Sample macro with faulty variable evaluations." 
    `(do ((,var ,start (+ ,var ,delta)) 
     (limit ,stop)) 
     ((> ,var limit) (- ,stop ,start)) 
    ,@body)) 

예를 들어, (do-range (i 1 15 3) (format t "~A " i))1 4 7 10 13를 인쇄 한 후 14을 반환해야합니다.

문제는 1) 자유 변수로 발생하기 때문에 limit의 두 번째 어커런스의 잠재적 캡처, 2) 바운드 변수 limit의 초기 어커런스의 잠재적 캡처는 다른 변수와 함께 표현식에서 발생하기 때문에은 stop이 매개 변수 목록에 delta 앞에 표시 되더라도 stopstart이 두 번 이상 평가되므로 여러 변수 평가가 수행 되더라도 매크로 평가에서 나타나는 오류가 발생합니다. 내가 알고있는 것처럼, once-only는 이러한 문제를 해결해야합니다

(defmacro do-range ((var start stop delta) &body body) 
    (once-only (start stop delta limit) 
    `(do ((,var ,start (+ ,var ,delta)) 
      (limit ,stop)) 
     ((> ,var limit) (- ,stop ,start)) 
     ,@body))) 

그러나, (macroexpand '(do-range (i 1 15 3) (format t "~A " i)))에 대한 limit 언 바운드 변수 인 뿌려줍니다. 위에서 with-gensyms으로 전환하면 1 & 2의 문제 만 처리해야합니다. 확장은 아무런 문제없이 진행됩니다.

once-only 매크로에 문제가 있습니까? 그리고 once-only은 실제로 위에 설명한 모든 문제 (그리고 아마도 다른 문제)를 실제로 해결합니까?

+1

왜 전혀'limit'을해야합니까 ? 'once-only' 버전에서 모든 사용을', stop'으로 대체 할 수 없었습니까? – melpomene

+0

@melpomene 네, 맞습니다. 그러나 결함이있는 매크로 (위의 문제 1과 2로 나열)에 두 가지 기본 종류 변수 캡처 문제를 도입하는 더 간단한 방법을 생각할 수 없습니다. 다른 아이디어? – davypough

답변

5

한때 만 매크로

N이되지 않는 것을 경고 제거하기 위해, 나는에 매크로 변경됩니다 :이 매크로의 목적이 있는지 확인하는 것입니다

(defmacro once-only ((&rest names) &body body) 
    (let ((gensyms (loop for nil in names collect (gensym)))) 
          ; changed N to NIL, NIL is ignored 
    `(let (,@(loop for g in gensyms collect `(,g (gensym)))) 
     `(let (,,@(loop for g in gensyms for n in names collect ``(,,g ,,n))) 
     ,(let (,@(loop for n in names for g in gensyms collect `(,n ,g))) 
      ,@body))))) 

을 표현식은 한 번만 정의 된 순서로 평가됩니다. 이를 위해 새로운 uninterned 변수를 도입하고 평가 결과를 변수에 바인딩합니다. 매크로 내부에서 새로운 변수를 사용할 수 있습니다. 매크로 자체는 매크로 작성을 쉽게하기 위해 제공됩니다. DO-RANGE에 한 번만 사용

ONCE-ONLY 귀하의 사용 예제 :

(defmacro do-range ((var start stop delta) &body body) 
    (once-only (start stop delta limit) 
    `(do ((,var ,start (+ ,var ,delta)) 
      (limit ,stop)) 
     ((> ,var limit) (- ,stop ,start)) 
     ,@body))) 

once-only 목록에이 LIMIT입니까? limit은 정의되지 않았습니다. LIMITONCE-ONLY 양식 내에서 기호로 사용되지만 바깥쪽에는 바인딩이 없습니다.

ONCE-ONLY은 이름 목록이 기호 목록이고 이러한 이름이 양식에 바인딩되어 있다고 예상합니다. 귀하의 경우 limit은 기호이지만 정의되지 않았습니다.

우리는 이름 목록에서 limit를 제거해야합니다 : 이제

(defmacro do-range ((var start stop delta) &body body) 
    (once-only (start stop delta) 
    `(do ((,var ,start (+ ,var ,delta)) 
      (limit ,stop)) 
     ((> ,var limit) (- ,stop ,start)) 
     ,@body))) 

에 대한 LIMIT을 무엇을? once-onlySTOP을 위해 포함, 이름에 대한 바인딩을 제공 감안할 때, 우리는 기호 LIMIT을 제거 할 수 및 ,stop과의 사용을 대체 :

(defmacro do-range ((var start stop delta) &body body) 
    (once-only (start stop delta) 
    `(do ((,var ,start (+ ,var ,delta))) 
     ((> ,var ,stop) (- ,stop ,start)) 
     ,@body))) 

예 :

CL-USER 137 > (pprint 
       (macroexpand 
       '(do-range (i 4 10 2) 
        (print i)))) 

(LET ((#1=#:G2170 4) 
     (#3=#:G2171 10) 
     (#2=#:G2172 2)) 
    (DO ((I #1# (+ I #2#))) 
     ((> I #3#) (- #3# #1#)) 
    (PRINT I))) 
+0

완벽하게 작동합니다. 그러나 내 이해를 명확히하기 위해 do에서 '제한'을 제거 할 필요는 없습니다. 'once-only' 변수에서 제거하면 원래의 잘못된 매크로가 작동합니다. – davypough

+0

@davypough :하지만 LIMIT은 본문에서 볼 수 있으며 다른 LIMIT의 그림자 일 수도 있습니다. 그것은 우리가 보통 피하기 원하는 것입니다. –