((eval thing-id) (eval x-id))
그냥 비교하기 ((eval thing-id) (eval (syntax-e x-id)))
을 변경하여 몇 분이 게시 한 후이를 수정 : 여기 당신이 컴파일시 단계에 떨어져 대부분의 작업을 추진하는 경우와 같은 매크로 외모 :
#lang racket
(require (for-syntax racket/format
racket/syntax))
(struct Person (name location))
(define-syntax (what stx)
(syntax-case stx (is)
((what is x thing)
#'(what is x's thing))
((what is x quote-s thing)
(let* ((thing-str (~a (syntax-e #'thing)))
(thing-proper-str (cadr (regexp-match #rx"(.*)\\?" thing-str)))
(thing-id (format-id stx "Person-~a" thing-proper-str)))
#`(#,thing-id x)))))
(define John (Person "John Luser" "Here"))
(what is John's name?)
한 가지주의해야 할 점은 (require (for-syntax ...))
은 프로그램의 소스 코드를 사전 처리하는 동안 컴파일 할 때 라이브러리를 사용하기 때문입니다.
또한 실제로는 x
이 실행되고 있지 않으므로 출력 구문에 보존 된 구문을 그대로 유지할 수 있기 때문에 조금 더 간단합니다.
도움이된다면 다른 언어로 경험해보십시오. 당신이 자바에 익숙하다면 표현식을 프로그램의 어딘가에 작성하고 프로그램을 컴파일하면 어떻게되는지 생각해보십시오. Java 컴파일러가 실행될 때 "hello "
문자열, "world"
문자열 및 두 문자열 간의 문자열 연결을 생성하는 바이트 코드를 생성 할 것으로 기대합니까? 대부분의 경우 "아니오, 컴파일러는 컴파일의 일부로 리터럴 "hello world"
로 다시 작성해야합니다."
이것은 전 처리입니다. Racket에 매크로를 작성하면 Racket에게 이러한 전처리 규칙을 더 잘 가르치고 있습니다.
여기서 우리는 문자열을 연결하는 방법을 알고있는 cat
명령을 추가 할 것입니다. 즉,이 연결 예제를 구현해 봅시다.
#lang racket
(define (cat x y)
(string-append x y))
(define msg-head "hello ")
(define msg-tail " world")
(cat msg-head msg-tail)
(cat "hiya " "world")
지금, 우리는 일반 함수로, cat
모두 사용이 여기에 결국 아래로 string-append
함수 호출을 줄일 것을 알고 :
는 다음 버전의 하나입니다. 컴파일러는 cat
의 두 번째 사용 방법을 만지는 방법을 알만큼 똑똑하지 않습니다.
하지만 같은 종류의 컴파일러 다시 쓰기 규칙을 수행하려는 경우 컴파일러에서 소스 코드가 cat
2 개의 문자열 리터럴을 사용하려고하는 경우 컴파일러에서 컴파일하려고하면 컴파일 타임에 컴파일되지 않습니다. 바이트 코드가 더 좋습니까?
음, 그렇게하겠습니다. 버전 두 사람은 다음과 같습니다
#lang racket
(define-syntax (cat stx)
(syntax-case stx()
[(_ x y)
;; At this point, the following code is being run by the _compiler_.
(cond
;; If in the source code we're transforming, both x and y are
;; strings, we can do the concatenation at _compile time_.
[(and (string? (syntax-e #'x))
(string? (syntax-e #'y)))
(let ([xy
(string-append (syntax-e #'x) (syntax-e #'y))])
;; Once we have this string, we still need to emit it back as the
;; result of the rewrite. We want to produce a piece of _syntax_
;; in place of the original stx.
(datum->syntax stx xy))]
[else
;; Otherwise, we want to produce a piece of syntax that looks like a
;; plain function call to string-append.
(datum->syntax stx (list 'string-append #'x #'y))])]))
(define msg-head "hello ")
(define msg-tail " world")
(cat msg-head msg-tail)
(cat "hiya " "world")
이제 변환 이런 종류의 문제가 관찰 중 하나입니다 : 우리가이 권리를 한 경우에, 아무도 그 차이를 말할 수 없을 것! : P 그렇지 않으면 깨진 변형이됩니다.차이점을보다 쉽게 보려면 DrRacket 도구 모음에서 매크로 스텝을 매크로 버튼을 누릅니다. 컴파일러를 호출하지만 바이트 코드로 바뀌기 직전에 프로그램에서 어떤 변형이 발생했는지 보여줍니다.
참고 : 매크로가있는 방식으로 모든 계산이 런타임에 완료됩니다. 일반적으로 컴파일 타임에 가능한 한 많이 밀어 넣기를 원합니다. '(John의 이름은 무엇입니까?)'에서'(Person-name John)'이라는 용어를 컴파일 할 때 컴파일을 할 수 있습니다. Greg Hendershott의 Macro of Fear와 같은 튜토리얼을보고 싶을 것입니다 : http://www.greghendershott.com/fear-of-macros/ – dyoo
'eval' 접근법은 또한 깨지기 쉽습니다. 반드시 당신이 잘 통제 할 수없는 환경에서의 동적 인 평가. 나는 DrRacket에서 텍스트 REPL 대 독립 실행 형 프로그램을 실행하여 결과가 달라질 것이라고 생각합니다. 또한 Racket에서 할 수있는 멋진 컴파일 타임 기능을 활용하지 않으므로 다른 옵션이 없다면'eval'을 피하십시오. – dyoo
@dyoo 유감스럽게도 메타 프로그래밍은 너무 복잡하여 다른 옵션이 무엇인지 알지 못하고 어떻게 작동하는지, 어떻게 사용하는지 알지 못합니다. –