많은 Lispers는 Lisp를 특별하게 만드는 것이 homoiconicity 라고 말하는데 , 이는 코드의 구문이 다른 데이터와 동일한 데이터 구조를 사용하여 표현됨을 의미합니다. 예를 들어, 주어진 측면 길이를 가진 직각 삼각형의 빗변을 계산하는 간단한 함수 (구성표 구문 사용)는 다음과 같습니다.
(define (hypot x y)
(sqrt (+ (square x) (square y))))
이제 호모 닉은 위의 코드가 실제로 Lisp 코드에서 데이터 구조 (특히 목록 목록)로 표현 될 수 있다고 말합니다. 따라서 다음 목록을 고려하여 이들이 어떻게 "접착"되는지 확인하십시오.
(define #2# #3#)
(hypot x y)
(sqrt #4#)
(+ #5# #6#)
(square x)
(square y)
매크로를 사용하면 소스 코드를 다음과 같이 처리 할 수 있습니다. 이들 6 "하위 목록"은 각각 다른리스트에 하나의 포인터를 포함하고, 또는 심볼들 (이 예에서 : define
, hypot
, x
, y
, sqrt
, +
, square
).
그렇다면 어떻게 호모 닉을 사용하여 구문을 "고르고"매크로를 만들 수 있습니까? 다음은 간단한 예입니다. let
매크로를 다시 구현해 보겠습니다 my-let
. 알림으로
(my-let ((foo 1)
(bar 2))
(+ foo bar))
로 확장해야
((lambda (foo bar)
(+ foo bar))
1 2)
Scheme "명시 적 이름 바꾸기"매크로 †를 사용한 구현은 다음과 같습니다 .
(define-syntax my-let
(er-macro-transformer
(lambda (form rename compare)
(define bindings (cadr form))
(define body (cddr form))
`((,(rename 'lambda) ,(map car bindings)
,@body)
,@(map cadr bindings)))))
form
매개 변수는 실제 형식에 바인딩, 그래서 우리의 예를 들어, 그것은 것입니다 (my-let ((foo 1) (bar 2)) (+ foo bar))
. 따라서 예제를 살펴 보겠습니다.
- 먼저 폼에서 바인딩을 검색합니다. 양식
cadr
의 ((foo 1) (bar 2))
일부를 잡습니다 .
그런 다음 양식에서 본문을 검색합니다. 양식 cddr
의 ((+ foo bar))
일부를 잡습니다 . (이것은 바인딩 후 모든 하위 폼을 잡기위한 것이므로 폼이
(my-let ((foo 1)
(bar 2))
(debug foo)
(debug bar)
(+ foo bar))
몸이 될 것입니다 ((debug foo) (debug bar) (+ foo bar))
.)
- 이제 우리는 실제로 결과
lambda
표현을 만들고 수집 한 바인딩과 본문을 사용하여 호출합니다. 백틱 (backtick)은 "quasiquote"라고하며, 쿼마 (Quasiquote) 내부의 모든 것을 쉼표 뒤의 비트 ( "unquote")를 제외하고 리터럴 데이텀으로 취급 합니다.
(rename 'lambda)
수단은 사용 lambda
이 매크로가 될 때 힘에 바인딩을 정의 , 오히려 어떤 것보다 lambda
이 매크로 때 주위에있을 수있는 바인딩을 사용 . (이것은 위생이라고 합니다.)
(map car bindings)
반환 값 (foo bar)
: 각 바인딩의 첫 번째 데이텀.
(map cadr bindings)
반환 값 (1 2)
: 각 바인딩의 두 번째 데이텀.
,@
리스트를 리턴하는 표현식에 사용되는 "접합"을 수행합니다.리스트 자체가 아니라리스트의 요소를 결과에 붙여 넣습니다.
- 그 결과, 목록으로 함께, 우리가 얻을 모든 것을, 퍼팅
(($lambda (foo bar) (+ foo bar)) 1 2)
, $lambda
이름을 바꾼를 말한다 여기 lambda
.
간단 하죠? ;-) (직접적이지 않다면 다른 언어의 매크로 시스템을 구현하는 것이 얼마나 어려울 지 상상해보십시오.)
따라서 소스 코드를 복잡하지 않은 방식으로 "분리"할 수있는 방법이 있다면 다른 언어에 대한 매크로 시스템을 가질 수 있습니다. 이것에 대한 몇 가지 시도가 있습니다. 예를 들어 sweet.js 는 JavaScript에 대해이 작업을 수행합니다.
† 노련한 Schemers가 이것을 읽으려고 의도적으로, defmacro
다른 Lisp 방언이 사용하는 것 사이 의 중간 타협으로 명시 적 이름 바꾸기 매크로를 사용하기로 선택했습니다 syntax-rules
. 나는 다른 Lisp 방언으로 글을 쓰고 싶지 않지만, 익숙하지 않은 비 화학자들을 소외하고 싶지 않습니다 syntax-rules
.
참고로 다음과 같은 my-let
매크로를 사용합니다 syntax-rules
.
(define-syntax my-let
(syntax-rules ()
((my-let ((id val) ...)
body ...)
((lambda (id ...)
body ...)
val ...))))
해당 syntax-case
버전은 매우 유사합니다.
(define-syntax my-let
(lambda (stx)
(syntax-case stx ()
((_ ((id val) ...)
body ...)
#'((lambda (id ...)
body ...)
val ...)))))
둘 사이의 차이는 즉 모든 에 syntax-rules
암시가 #'
적용 당신이 할 수 있도록 만 의 패턴 / 템플릿 쌍을 syntax-rules
따라서 완전히 선언이다. 반대로,에서 syntax-case
패턴 뒤의 비트는 실제로 구문 #'(...)
코드 ( ) 를 반환해야 하지만 다른 코드도 포함 할 수있는 실제 코드입니다 .