위생 매크로
Hygienic macro이 글은 대부분의 독자들이 이해하기에는 너무 기술적인 것일 수도 있다.(2016년 11월)(이를 과 시기 |
위생적인 매크로는 확장이 보장된 매크로로 식별자의 우발적인 포획을 초래하지 않는다.그것들은 Scheme,[1] Dylan,[2] Rust, Nim, Julia와 같은 프로그래밍 언어의 특징이다.우발적인 포획의 일반적인 문제는 위생적인 매크로가 도입되기 전에 Lisp 커뮤니티 내에 잘 알려져 있었다.매크로 작성자는 문제를 피하기 위해 고유한 식별자(예: gensym)를 생성하거나 난독화된 식별자를 사용하는 언어 기능을 사용할 것이다.위생적인 매크로(macro)는 매크로 확장기 자체에 통합되어 있는 포획 문제에 대한 프로그램적인 해결책이다.'하이진'이라는 용어는 1986년 콜베커 외 논문에서 수학에서 사용되는 용어에서 영감을 받아 위생적인 거시적 확장을 도입한 용어에서 유래되었다.[3]
위생문제
비위생적인 매크로 시스템을 가진 프로그래밍 언어에서는 기존 가변 바인딩이 확장되는 동안 생성되는 가변 바인딩에 의해 매크로에서 숨겨지는 것이 가능하다.C에서 이 문제는 다음과 같은 파편으로 설명할 수 있다.
#define INCI(i) do {int a=0; ++i; } while (0) 인트로 본래의(공허하게 하다) { 인트로 a = 4, b = 8; INCI(a); INCI(b); 활자화하다("a는 현재 %d이고, b는 현재 %d입니다.\n", a, b); 돌아오다 0; } C 전처리기기를 통해 위의 내용을 실행하면 다음과 같은 결과가 나온다.
인트로 본래의(공허하게 하다) { 인트로 a = 4, b = 8; 하다 { 인트로 a = 0; ++a; } 하는 동안에 (0); 하다 { 인트로 a = 0; ++b; } 하는 동안에 (0); 활자화하다("a는 현재 %d이고, b는 현재 %d입니다.\n", a, b); 돌아오다 0; } 변수a상위 범위에 선언된 것은 에 의해 그늘에 가려진다.a새로운 범위를 도입하는 매크로에서 변수.결과적으로, 컴파일된 프로그램의 출력이 다음과 같이 나타나듯이, 프로그램의 실행에 의해 변경되지 않는다.
a는 지금 4이고 b는 지금 9이다.
가장 간단한 해결책은 현재 프로그램의 어떤 변수와 충돌하지 않는 매크로 변수 이름을 지정하는 것이다.
#define INCI(i) do { INCIa = 0; ++i; } while (0) 인트로 본래의(공허하게 하다) { 인트로 a = 4, b = 8; INCI(a); INCI(b); 활자화하다("a는 현재 %d이고, b는 현재 %d입니다.\n", a, b); 돌아오다 0; } 변수 이름이 지정될 때까지INCIa생성된 이 솔루션은 올바른 출력을 생성하며,
a는 지금 5, b는 지금 9이다.
현재 프로그램에서는 문제가 해결되지만 이 해결책은 탄탄하지 않다.매크로 내부에서 사용되는 변수와 프로그램의 나머지 변수의 변수는 프로그래머에 의해 동기화되어야 한다.특히 매크로 사용INCI변수에 의하여INCIa원래 매크로가 변수에 실패한 것과 동일한 방식으로 실패함a.
"하이진 문제"는 가변 바인딩을 넘어 확장될 수 있다.다음과 같은 공통 Lisp 매크로를 고려하십시오.
(디프매크로 아니면 (조건 &body 보디) `(만일 (아닌 ,조건) (기절시키다 ,@보디))) 이 매크로에는 변수에 대한 참조가 없지만 "if", "not" 및 "progn" 기호가 모두 통상적인 정의에 바인딩되어 있다고 가정한다.그러나 위의 매크로가 다음 코드에서 사용되는 경우:
(고기를 썰다 ((아닌 (x) x)) (아니면 t (형식을 갖추다 t "이것은 인쇄되어서는 안 된다!"))) "아닙니다"의 정의가 국지적으로 변경되어, 그 확대가 이루어졌다.my-unless(표준 기능 및 연산자를 재정렬하면, 세계적으로든 로컬로든, 실제로 ANSI Common Lisp에 따라 정의되지 않은 행동을 유발한다.그러한 사용은 시행에 의해 잘못된 것으로 진단될 수 있다.)
한편, 위생적인 매크로 시스템은 모든 식별자의 어휘 범위(예: "if", "not")를 자동으로 보존한다.이 속성을 참조 투명성이라고 한다.
물론 이 문제는 다음과 같은 방법으로 보호되지 않는 프로그램 정의 기능에 발생할 수 있다.
(디프매크로 아니면 (조건 &body 보디) `(만일 (사용자 정의 프로세서 ,조건) (기절시키다 ,@보디))) (고기를 썰다 ((사용자 정의 프로세서 (x) x)) (아니면 t (형식을 갖추다 t "이것은 인쇄되어서는 안 된다!"))) 이 문제에 대한 일반적인 Lisp 솔루션은 패키지를 사용하는 것이다.그my-unless매크로가 자신의 패키지에 위치할 수 있으며,user-defined-operator포장에 있는 개인 기호야기호user-defined-operator사용자 코드에서 발생하는 기호는 정의에 사용된 기호와 무관하게 다른 기호가 될 것이다.my-unless매크로의
한편, 위생적인 매크로를 사용하는 Scheme과 같은 언어는 우발적인 포획을 방지하고 매크로 확장 과정의 일부로 참조 투명성을 자동으로 보장한다.포획을 원하는 경우, 일부 시스템은 프로그래머가 매크로 시스템의 위생 메커니즘을 명시적으로 위반할 수 있도록 허용한다.
예를 들어, 다음 계획 구현:my-unless원하는 동작을 취할 것:
(정의를 내리다아니면 (구문 분석의() ((_ 조건 보디 ...) (만일(아닌조건) (시작되다보디 ...))))) (하게 하다((아닌(람다(x) x))) (아니면 #t (전시하다"이것은 인쇄되어서는 안 된다!") (뉴라인))) 위생매크로가 부족한 언어에 사용되는 전략
Lisp 언어 계열의 Common Lisp, Scheme 및 기타 언어에서 매크로는 언어를 확장하는 강력한 수단을 제공한다.여기서 재래식 매크로의 위생 부족은 몇 가지 전략으로 해결된다.
- 난독화
- 매크로를 확장하는 동안 임시 저장이 필요한 경우 매크로를 사용하는 프로그램에서 같은 이름이 절대로 사용되지 않기를 바라는 뜻에서 특이한 변수 이름을 사용할 수 있다.
- 임시 기호 생성
- 일부 프로그래밍 언어에서는 새로운 변수 이름 또는 기호를 생성하여 임시 위치에 바인딩할 수 있다.언어 처리 시스템은 이것이 실행 환경에서 다른 이름이나 위치와 충돌하지 않도록 보장한다.매크로 정의 본문 내에서 이 기능의 사용을 선택하는 책임은 프로그래머에게 있다.이 방법은 MacLisp에서 사용되었으며, 여기서 함수의 이름이
gensym새 기호 이름을 생성하는 데 사용될 수 있다.유사한 함수(일반적으로 명명됨)gensym또한) 널리 구현된 Common Lisp 표준[4] 및 Elisp를 포함하여 많은 Lisp 유사 언어로 존재한다. - 삽입되지 않은 읽기 시간 기호
- 이는 단일 이름이 동일한 매크로의 다중 확장에 의해 공유된다는 점에서 첫 번째 솔루션과 유사하다.그러나 특이한 이름과는 달리, 비내장된 읽기 시간 기호가 사용된다(에 의해 표시됨).
#:(기호)를 위해, 매크로 외부에서 발생할 수 없다. - 패키지
- 매크로는 특이한 이름이나 비인턴 기호 대신 매크로를 정의한 패키지의 개인 기호를 사용할 뿐이다.기호는 사용자 코드에서 우발적으로 발생하지 않는다.사용자 코드는 이중 콜론을 사용하여 패키지 내부에 도달해야 한다.
::예를 들어 개인 기호 사용 권한을 부여하기 위한 표기법cool-macros::secret-sym. 그 시점에서 우발적인 위생 부족의 문제는 무뚝뚝하다.따라서 Lisp 패키지 시스템은 이름 충돌의 한 예라고 볼 수 있는 매크로 위생 문제에 대한 실행 가능하고 완전한 해결책을 제공한다. - 위생적 변환
- 입력 양식의 패턴을 출력 양식으로 변환하는 것을 담당하는 프로세서가 기호 충돌을 감지하고 일시적으로 기호 이름을 변경하여 해결한다.이러한 종류의 프로세싱은 Scheme's의 지원을 받는다.
let-syntax그리고define-syntax매크로 생성 시스템.기본 전략은 매크로 정의에서 바인딩을 식별하고 이러한 이름을 젠시ms로 대체하며, 매크로 정의에서 자유 변수를 식별하고 매크로가 사용된 범위 대신 매크로 정의 범위에서 해당 이름을 조회하도록 하는 것이다. - 리터럴 객체
- 일부 언어에서 매크로의 확장은 기호를 포함하는 표현으로 확장하는 것이 아니라 텍스트 코드에 대응하지 않아도 된다.
f, 매크로가 참조한 실제 객체를 포함하는 확장을 생성할 수 있다.f마찬가지로 매크로가 매크로의 패키지에 정의된 로컬 변수나 객체를 사용해야 하는 경우, 어휘 환경을 둘러싸는 것이 매크로 정의의 그것인 폐쇄 객체의 호출로 확장될 수 있다.
구현
자동으로 위생을 시행하는 매크로 시스템(Scheme)은 Scheme에서 시작되었다.위생적인 매크로 시스템에 대한 원래의 알고리즘(KFFD 알고리즘)은 86년에 콜베커에 의해 제시되었다.[3]당시, 어떤 표준 매크로 시스템도 Scheme 구현에 의해 채택되지 않았다.그 직후인 87년 콜베커와 완드는 매크로를 쓰기 위한 선언적 패턴 기반 언어를 제안했는데, 이것이 마크로의 전신이었다.syntax-rulesR5RS 표준에 의해 채택된 매크로 시설.[1][5]대체 위생 메커니즘인 통폐쇄는 88년 바우덴과 리스에 의해 콜베커 외 시스템의 대안으로 제안되었다.[6]KFFD 알고리즘과 달리, 구문 폐쇄는 프로그래머가 식별자의 범위 분해능을 명시적으로 명시하도록 요구한다.1993년, Dybvig 등이 소개한 바 있다.syntax-case구문의 대체 표현을 사용하고 자동 위생 상태를 유지하는 매크로 시스템.[7]그syntax-case시스템은 다음을 표현할 수 있다.syntax-rules언어를 파생된 매크로로 패턴화한다.
매크로 시스템이라는 용어는 체계 맥락에서 패턴 매칭 구조(예: 구문 규칙)와 구문을 나타내고 조작하는 프레임워크(예: 구문-사례, 구문 폐쇄)를 모두 참조할 수 있기 때문에 모호할 수 있다.구문-규칙은 매크로를 쓰기 쉽게 만드는 고도의 패턴 매칭 시설이다.하지만syntax-rules매크로의 특정 클래스를 간결하게 설명할 수 없고 다른 매크로 시스템을 표현하기에 불충분하다.구문 규칙은 R4RS 문서에 부록으로 설명되었지만 강제되지는 않았다.이후 R5RS는 이를 표준 매크로 시설로 채택했다.여기 예가 있다.syntax-rules두 변수의 값을 스왑하는 매크로:
(정의를 내리다교환! (구문 분석의() ((_ a b) (하게 하다((임시 변통하다 a)) (세트!a b) (세트!b 임시 변통하다))))) 순전히 한 사람의 결함으로 인해.syntax-rules매크로 시스템을 기반으로, 낮은 수준의 매크로 시스템도 제안되고 실행되었다.구문 케이스는 그러한 시스템 중 하나이다.와는 달리syntax-rules,syntax-case패턴 매칭 언어와 낮은 수준의 매크로 쓰기 기능이 모두 포함되어 있다.전자는 선언적으로 매크로를 쓸 수 있게 하고 후자는 매크로를 쓸 수 있는 대체 프론트의 구현을 허용한다.이전의 스왑 예제가 다음에서 거의 동일함syntax-case패턴 일치 언어가 유사하기 때문에:
(정의를 내리다교환! (람다(stx) (구문 대소문자 stx () ((_ a b) (구문 (하게 하다((임시 변통하다 a)) (세트!a b) (세트!b 임시 변통하다))))))) 하지만syntax-case구문 분석보다 더 강력하다.예를 들어,syntax-case매크로는 임의의 Scheme 함수를 통해 패턴 일치 규칙에 측면 조건을 지정할 수 있다.또는 매크로 작성자는 프런트엔드와 일치하는 패턴을 사용하지 않고 구문을 직접 조작할 수 있다.사용datum->syntax기능, 구문-케이스 매크로 또한 의도적으로 식별자를 포착하여 위생을 파괴할 수 있다.R6RS Scheme 표준은 구문-사례 매크로 시스템을 채택하였다.[8]
통사적 폐쇄와 명시적 개명은[9] 다른 두 가지 대안적인 매크로 시스템이다.두 시스템은 모두 구문 규칙보다 수준이 낮으며 위생 집행은 매크로 작성자에게 맡긴다.이는 구문 규칙과 구문 사례 모두 다르며, 기본적으로 위생을 자동으로 시행한다.위의 스왑 예는 각각 통사적 폐쇄와 명시적 명칭 변경 구현을 사용하여 여기에 제시된다.
;; 통사적 폐쇄 (정의를 내리다교환! (sc-flashed (람다(형체를 이루다 환경) (하게 하다((a (근접 촬영의 (사관생도형체를 이루다) 환경)) (b (근접 촬영의 (캐드러형체를 이루다) 환경))) `(하게 하다((임시 변통하다 ,a)) (세트!,a ,b) (세트!,b 임시 변통하다)))))) ;; 명시적 이름 변경 (정의를 내리다교환! (에르-아프리카의 (람다(형체를 이루다 이름을 바꾸다 비교하다) (하게 하다((a (사관생도형체를 이루다)) (b (캐드러형체를 이루다)) (임시 변통하다 (이름을 바꾸다 'temp))) `(,(이름을 바꾸다 렛츠) ((,임시 변통하다 ,a)) (,(이름을 바꾸다 '셋트!) ,a ,b) (,(이름을 바꾸다 '셋트!) ,b ,임시 변통하다)))))) 위생적인 매크로 시스템을 갖춘 언어
- 체계 – 구문 규칙, 구문-케이스, 구문-폐쇄 등
- 라켓 – 계략의 한 장면.그것의 매크로 시스템은 원래 구문-사례를 기반으로 했지만, 지금은 더 많은 기능을 가지고 있다.
- 네멜레[10]
- 딜런
- 엘리시르[11]
- 님
- 녹
- 헥시
- Mary2 – 1978년경 알골68-파생 언어로 표시된 매크로 본체
- 줄리아.[12]
- 라쿠 – 위생 및 비위생적인 매크로[13] 지원
비판
위생적인 매크로는 매크로의 힘을 제한하는 비용으로 프로그래머에게 약간의 안전을 제공한다.결과적으로, Common Lisp 매크로는 Scheme 매크로보다 훨씬 더 강력하다.Let Over Lambda의 저자인 더그 호이트는 다음과 같이 말했다.[14]
가변 포획의 영향을 줄이기 위해 취해진 거의 모든 접근법은 디프마크로로 할 수 있는 것을 줄이는 데만 도움이 된다.위생적인 매크로는 최상의 상황에서 초심자의 안전 가드 레일이다. 최악의 상황에서는 전기 울타리를 형성하여 피해자들을 위생적이고 포획이 안전한 감옥에 가두어 버린다.
— Doug Hoyte
참고 항목
메모들
- ^ a b Richard Kelsey; William Clinger; Jonathan Rees; et al. (August 1998). "Revised5 Report on the Algorithmic Language Scheme". Higher-Order and Symbolic Computation. 11 (1): 7–105. doi:10.1023/A:1010051815785.
- ^ Feinberg, N.; Keene, S. E.; Matthews, R. O.; Withington, P. T. (1997), Dylan programming: an object-oriented and dynamic language, Addison Wesley Longman Publishing Co., Inc.
- ^ a b Kohlbecker, E.; Friedman, D. P.; Felleisen, M.; Duba, B. (1986). "Hygienic Macro Expansion" (PDF). ACM conference on LISP and functional programming.
- ^ "CLHS: Function GENSYM".
- ^ Kohlbecker, E; Wand, M (1987). "Macro-by-example: Deriving syntactic transformations from their specifications" (PDF). Symposium on Principles of Programming Languages.
- ^ Bawden, A; Rees, J (1988). "Syntactic closures" (PDF). Lisp and Functional Programming.
- ^ Dybvig, K; Hieb, R; Bruggerman, C (1993). "Syntactic abstraction in Scheme" (PDF). Lisp and Symbolic Computation. 5 (4): 295–326. doi:10.1007/BF01806308.
- ^ Sperber, Michael; Dybvig, R. Kent; Flatt, Matthew; Van Straaten, Anton; et al. (August 2007). "Revised6 Report on the Algorithmic Language Scheme (R6RS)". Scheme Steering Committee. Retrieved 2011-09-13.
- ^ Clinger, Will (1991). "Hygienic macros through explicit renaming". ACM SIGPLAN Lisp Pointers. 4 (4): 25–28. doi:10.1145/1317265.1317269.
- ^ Skalski, K.; Moskal, M; Olszta, P, Metaprogramming in Nemerle (PDF), archived from the original (PDF) on 2012-11-13
- ^ "Macros".
- ^ "Metaprogramming · the Julia Language".
- ^ "Synopsis 6: Subroutines". Archived from the original on 2014-01-06. Retrieved 2014-06-03.
- ^ [1], Let Over Lambda—Doug Hoyte가 지은이