프로그래밍 언어에 대한 Paul Graham의 에세이 를 읽으면 Lisp 매크로 가 유일한 방법 이라고 생각할 것입니다 . 다른 플랫폼에서 일하는 바쁜 개발자로서 Lisp 매크로를 사용할 권한이 없었습니다. 버즈를 이해하려는 사람은이 기능이 왜 그렇게 강력한 지 설명하십시오.
또한 파이썬, Java, C # 또는 C 개발의 세계에서 이해할 수있는 것과 관련이 있습니다.
프로그래밍 언어에 대한 Paul Graham의 에세이 를 읽으면 Lisp 매크로 가 유일한 방법 이라고 생각할 것입니다 . 다른 플랫폼에서 일하는 바쁜 개발자로서 Lisp 매크로를 사용할 권한이 없었습니다. 버즈를 이해하려는 사람은이 기능이 왜 그렇게 강력한 지 설명하십시오.
또한 파이썬, Java, C # 또는 C 개발의 세계에서 이해할 수있는 것과 관련이 있습니다.
답변:
짧은 대답을 제공하기 위해 매크로는 공통 Lisp 또는 DSL (Domain Specific Languages)에 대한 언어 구문 확장을 정의하는 데 사용됩니다. 이러한 언어는 기존 Lisp 코드에 바로 포함됩니다. 이제 DSL은 Lisp와 유사한 구문 (예 : Peter Norvig의 Common Lisp 의 Prolog Interpreter ) 또는 완전히 다른 (예 : Clojure의 Infix Notation Math) 을 가질 수 있습니다 .
좀 더 구체적인 예는 다음과 같습니다.
파이썬에는 언어에 내장 된 목록 이해력이 있습니다. 이것은 일반적인 경우에 대한 간단한 구문을 제공합니다. 라인
divisibleByTwo = [x for x in range(10) if x % 2 == 0]
0에서 9 사이의 모든 짝수를 포함하는 목록을 생성합니다. Python 1.5 에서 그 와 같은 구문은 없었습니다. 다음과 같은 것을 사용하십시오.
divisibleByTwo = []
for x in range( 10 ):
if x % 2 == 0:
divisibleByTwo.append( x )
이들은 기능적으로 동일합니다. 불신의 서스펜션을 시작하고 Lisp에 매우 제한된 루프 매크로가 있다고 가정 해 봅시다. 반복을 수행하고 목록 이해에 해당하는 쉬운 방법은 없습니다.
Lisp에서 다음을 작성할 수 있습니다. 이 고안된 예제는 Lisp 코드의 좋은 예제가 아닌 Python 코드와 동일하도록 선택되었습니다.
;; the following two functions just make equivalent of Python's range function
;; you can safely ignore them unless you are running this code
(defun range-helper (x)
(if (= x 0)
(list x)
(cons x (range-helper (- x 1)))))
(defun range (x)
(reverse (range-helper (- x 1))))
;; equivalent to the python example:
;; define a variable
(defvar divisibleByTwo nil)
;; loop from 0 upto and including 9
(loop for x in (range 10)
;; test for divisibility by two
if (= (mod x 2) 0)
;; append to the list
do (setq divisibleByTwo (append divisibleByTwo (list x))))
더 나아 가기 전에 매크로가 무엇인지 더 잘 설명해야합니다. 코드에 의해 수행되는 변환입니다. 코드 입니다. 즉, 코드를 인수로 받아들이는 인터프리터 (또는 컴파일러)가 읽은 코드 조각은 결과를 조작하고 결과를 반환하며 그 자리에서 실행됩니다.
물론 그것은 많은 타이핑이고 프로그래머는 게으르다. 그래서리스트 이해를위한 DSL을 정의 할 수있었습니다. 실제로, 우리는 이미 하나의 매크로 (루프 매크로)를 사용하고 있습니다.
Lisp는 몇 가지 특수 구문 형식을 정의합니다. 따옴표 ( '
)는 다음 토큰이 리터럴임을 나타냅니다. 준 따옴표 또는 역 따옴표 ( `
)는 다음 토큰이 이스케이프 된 리터럴임을 나타냅니다. 이스케이프는 쉼표 연산자로 표시됩니다. 리터럴 '(1 2 3)
은 Python과 동일 [1, 2, 3]
합니다. 다른 변수에 지정하거나 대신 사용할 수 있습니다. 이전에 정의 된 변수 `(1 2 ,x)
인 파이썬의 [1, 2, x]
위치 와 동등한 것으로 생각할 수 있습니다 x
. 이 목록 표기법은 매크로에 들어가는 마술의 일부입니다. 두 번째 부분은 코드를 지능적으로 대체하는 Lisp 판독기이지만 아래에 가장 잘 설명되어 있습니다.
따라서 lcomp
(목록 이해의 줄임말) 이라는 매크로를 정의 할 수 있습니다 . 이 구문은 정확히 우리가 예에서 사용하는 파이썬처럼 될 것입니다 [x for x in range(10) if x % 2 == 0]
-(lcomp x for x in (range 10) if (= (% x 2) 0))
(defmacro lcomp (expression for var in list conditional conditional-test)
;; create a unique variable name for the result
(let ((result (gensym)))
;; the arguments are really code so we can substitute them
;; store nil in the unique variable name generated above
`(let ((,result nil))
;; var is a variable name
;; list is the list literal we are suppose to iterate over
(loop for ,var in ,list
;; conditional is if or unless
;; conditional-test is (= (mod x 2) 0) in our examples
,conditional ,conditional-test
;; and this is the action from the earlier lisp example
;; result = result + [x] in python
do (setq ,result (append ,result (list ,expression))))
;; return the result
,result)))
이제 명령 행에서 실행할 수 있습니다 :
CL-USER> (lcomp x for x in (range 10) if (= (mod x 2) 0))
(0 2 4 6 8)
꽤 깔끔하지? 이제 거기서 멈추지 않습니다. 원한다면 메카니즘이나 붓이 있습니다. 원하는 구문을 사용할 수 있습니다. 파이썬이나 C #의 with
구문 처럼 . 또는 .NET의 LINQ 구문. 결국 이것이 궁극적 인 유연성 인 사람들을 Lisp로 끌어들이는 것입니다.
(loop for x from 0 below 10 when (evenp x) collect x)
, 여기에 더 많은 예제를 . 그러나 실제로 루프는 "단순한 매크로"입니다 (실제로 처음부터 처음부터 다시 구현했습니다 )
여기 에서 lisp macro에 관한 포괄적 인 토론을 찾을 수 있습니다 .
그 기사의 흥미로운 부분 집합 :
대부분의 프로그래밍 언어에서 구문은 복잡합니다. 매크로는 프로그램 구문을 분해하고 분석 한 후 재 조립해야합니다. 그들은 프로그램의 파서에 액세스 할 수 없으므로 휴리스틱과 베스트 추측에 의존해야합니다. 때로는 절삭 속도 분석이 잘못되어 파손됩니다.
그러나 Lisp는 다릅니다. Lisp 매크로 는 파서에 액세스 할 수 있으며 실제로는 간단한 파서입니다. Lisp 매크로는 문자열이 아니라 Lisp 프로그램의 소스가 문자열이 아니기 때문에 목록 형식의 소스 코드 조각으로 처리됩니다. 목록입니다. 그리고 Lisp 프로그램은 목록을 분해하고 다시 정리하는 데 능숙합니다. 그들은 매일 이것을 안정적으로 수행합니다.
다음은 확장 된 예입니다. Lisp에는 할당을 수행하는 "setf"라는 매크로가 있습니다. setf의 가장 간단한 형태는
(setf x whatever)
"x"기호의 값을 "whatever"식의 값으로 설정합니다.
Lisp에는 목록도 있습니다. "car"및 "cdr"함수를 사용하여 목록의 첫 번째 요소 또는 나머지 목록을 각각 가져올 수 있습니다.
이제 목록의 첫 번째 요소를 새 값으로 바꾸려면 어떻게해야합니까? 이를위한 표준 기능이 있으며, 그 이름은 "car"보다 훨씬 나쁩니다. "rplaca"입니다. 하지만 "rplaca"를 기억할 필요는 없습니다.
(setf (car somelist) whatever)
일부리스트의 자동차를 설정합니다.
여기서 실제로 일어나는 것은 "setf"가 매크로라는 것입니다. 컴파일 타임에 인수를 검사하고 첫 번째 인수가 (car SOMETHING) 형식임을 알 수 있습니다. "아, 프로그래머는 무언가의 차를 세우려고 노력하고있다. 그것을 위해 사용하는 기능은 'rplaca'이다." 그리고 다음과 같이 코드를 조용히 다시 작성합니다.
(rplaca somelist whatever)
일반적인 Lisp 매크로는 본질적으로 코드의 "구문 프리미티브"를 확장합니다.
예를 들어, C에서 switch / case 구문은 정수 유형에서만 작동하며 부동 소수점 또는 문자열에 사용하려는 경우 중첩 된 if 문과 명시 적 비교가 남습니다. C 매크로를 작성하여 작업을 수행 할 수있는 방법도 없습니다.
그러나 리스프 매크로는 코드 스 니펫을 입력으로 사용하고 매크로의 "호출"을 대체하기 위해 코드를 리턴하는 리스프 프로그램이기 때문에 원하는만큼 "기본"레퍼토리를 확장 할 수 있습니다. 더 읽기 쉬운 프로그램으로.
C에서 동일한 작업을 수행하려면 초기 (quite-C가 아닌) 소스를 먹고 C 컴파일러가 이해할 수있는 것을 뱉어내는 커스텀 프리 프로세서를 작성해야합니다. 그것에 대한 잘못된 길은 아니지만 반드시 가장 쉬운 것은 아닙니다.
리스프 매크로를 사용하면 어떤 부분이나 표현이 언제 평가 될 것인지 결정할 수 있습니다. 간단한 예를 들어 C를 생각해보십시오.
expr1 && expr2 && expr3 ...
이것이 말하는 것 : 평가 expr1
, 그리고 사실이라면 평가 expr2
등
이제 이것을 &&
함수로 만들어보십시오 ... 그렇습니다. 그렇습니다. 다음과 같은 것을 호출 :
and(expr1, expr2, expr3)
거짓 exprs
여부 expr1
에 관계없이 답을 산출하기 전에 세 가지 를 모두 평가합니다 !
lisp 매크로를 사용하면 다음과 같은 코드를 작성할 수 있습니다.
(defmacro && (expr1 &rest exprs)
`(if ,expr1 ;` Warning: I have not tested
(&& ,@exprs) ; this and might be wrong!
nil))
이제 당신은 &&
함수처럼 호출 할 수 있으며 모두 참이 아닌 한 전달하는 양식을 평가하지 않습니다.
이것이 어떻게 유용한 지 알아 보려면 대조하십시오.
(&& (very-cheap-operation)
(very-expensive-operation)
(operation-with-serious-side-effects))
과:
and(very_cheap_operation(),
very_expensive_operation(),
operation_with_serious_side_effects());
매크로를 사용하여 수행 할 수있는 다른 작업은 새 키워드 및 / 또는 미니 언어 ( (loop ...)
예 : 매크로 확인 )를 작성하고 다른 언어를 lisp에 통합하는 것입니다. 예를 들어 다음과 같이 말할 수있는 매크로를 작성할 수 있습니다.
(setvar *rows* (sql select count(*)
from some-table
where column1 = "Yes"
and column2 like "some%string%")
그리고 그것도 리더 매크로에 들어 가지 않습니다 .
도움이 되었기를 바랍니다.
(and ...
는 거짓으로 평가 될 때까지 표현식을 평가합니다. 거짓 평가에 의해 생성 된 부작용이 발생한다는 점에 유의하십시오. 후속 표현식 만 건너 뜁니다.
나는이 동료보다 Lisp 매크로가 더 잘 설명 된 것을 본 적이 없다고 생각한다 : http://www.defmacro.org/ramblings/lisp.html
매크로 및 템플릿을 사용하여 C 또는 C ++에서 수행 할 수있는 작업을 생각해보십시오. 반복 코드를 관리하는 데 매우 유용한 도구이지만 매우 심각한 방식으로 제한됩니다.
Lisp 및 Lisp 매크로는 이러한 문제를 해결합니다.
C ++에 능통 한 사람과 대화하고 템플릿 메타 프로그래밍에 필요한 모든 템플릿 퍼지를 배우는 데 걸린 시간을 물어보십시오. 또는 Modern C ++ Design 과 같은 (뛰어난) 책의 모든 미친 트릭 은 언어가 10 년 동안 표준화되었지만 실제 컴파일러 사이에서 디버깅하기가 어렵고 실제로는 이식성이 없습니다. 메타 프로그래밍에 사용하는 언어가 프로그래밍에 사용하는 언어와 동일하면 모든 것이 사라집니다!
lisp 매크로는 프로그램 조각을 입력으로 사용합니다. 이 프로그램 조각은 원하는 방식으로 조작하고 변형 할 수있는 데이터 구조입니다. 결국 매크로는 다른 프로그램 조각을 출력하며이 조각은 런타임에 실행됩니다.
C #에는 매크로 기능이 없지만 컴파일러가 코드를 CodeDOM 트리로 구문 분석하고이를 메소드로 전달한 다음이를 다른 CodeDOM으로 변환 한 다음 IL로 컴파일하는 경우에도 마찬가지입니다.
for each
-statement -clause using
, linq -expressions 등과 같은 "sugar"구문을 select
기본 코드로 변환하는 매크로 로 구현하는 데 사용할 수 있습니다 .
Java에 매크로가있는 경우 Sun에서 기본 언어를 변경하지 않고도 Linq 구문을 Java로 구현할 수 있습니다.
다음은 구현을위한 C #의 lisp 스타일 매크로가 어떻게 보이는지에 대한 의사 코드입니다 using
.
define macro "using":
using ($type $varname = $expression) $block
into:
$type $varname;
try {
$varname = $expression;
$block;
} finally {
$varname.Dispose();
}
기존의 답변은 매크로가 무엇을 달성하고 어떻게 설명하는지에 대한 구체적인 예를 제공하기 때문에 매크로 기능이 왜 다른 언어와 관련하여 중요한 이익이되는지에 대한 생각을 모으는 데 도움이 될 것 입니다 . 먼저 이러한 답변에서, 다른 곳에서 훌륭한 답변입니다.
... C에서, 당신은 [아마로 자격이 사용자 정의 전처리 작성해야 충분히 복잡한 C 프로그램을 ] ...
— 질
C ++에 능통 한 사람과 대화하고 템플릿 메타 프로그래밍을 수행하는 데 필요한 모든 템플릿 퍼지를 배우는 데 얼마나 오래 걸 렸는지 물어보십시오 [여전히 강력하지는 않습니다].
— 맷 커티스
... Java에서는 바이트 코드 직조로 길을 가야하지만 AspectJ와 같은 일부 프레임 워크에서는 다른 접근법을 사용 하여이 작업을 수행 할 수 있지만 근본적으로 해킹입니다.
— 미구엘 핑
DOLIST는 Perl의 foreach 또는 Python의 for와 유사합니다. Java는 JSR-201의 일부로 Java 1.5에서 "enhanced"for loop를 사용하여 유사한 종류의 루프 구성을 추가했습니다. 차이점 매크로가 무엇인지 확인하십시오. 코드에서 공통 패턴을 발견 한 Lisp 프로그래머는 매크로를 작성하여 해당 패턴의 소스 레벨 추상화를 제공 할 수 있습니다. 동일한 패턴을 발견 한 Java 프로그래머는 Sun에게이 특정 추상화가 언어에 추가 할 가치가 있음을 확신시켜야합니다. 그런 다음 Sun은 JSR을 게시하고 업계 전체의 "전문가 그룹"을 소집하여 모든 것을 해시해야합니다. Sun에 따르면이 프로세스는 평균 18 개월이 걸립니다. 그 후 컴파일러 작성자는 모두 새로운 기능을 지원하기 위해 컴파일러를 업그레이드해야합니다. Java 프로그래머가 선호하는 컴파일러가 새로운 버전의 Java를 지원하더라도 이전 버전의 Java와의 소스 호환성을 깨기 전에는 새로운 기능을 사용할 수 없을 것입니다. Common Lisp 프로그래머가 5 분 안에 스스로 해결할 수 있다는 성가심은 Java 프로그래머에게 몇 년 동안 전염됩니다.
모든 사람의 (우수한) 게시물에 통찰력을 추가 할 수 있을지 모르겠지만 ...
Lisp 매크로는 Lisp 구문 특성으로 인해 효과적입니다.
Lisp는 매우 규칙적인 언어입니다 (모든 것이 목록 이라고 생각 하십시오 ). 매크로를 사용하면 데이터와 코드를 동일하게 취급 할 수 있습니다 (리스프 표현식을 수정하는 데 문자열 구문 분석 또는 기타 해킹이 필요하지 않음). 이 두 기능을 결합하면 코드를 매우 깔끔하게 수정할 수 있습니다.
편집 : 내가 말하려고하는 것은 Lisp이 homoiconic 이므로 lisp 프로그램의 데이터 구조가 lisp 자체로 작성되었음을 의미합니다.
따라서 모든 기능을 갖춘 언어 자체를 사용하여 언어 위에 자신의 코드 생성기를 만드는 방법으로 끝납니다 (예 : Java에서는 AspectJ와 같은 일부 프레임 워크를 사용하면 바이트 코드 직조로 길을 열어야합니다) 다른 접근법을 사용 하여이 작업을 수행하십시오 (기본적으로 해킹입니다).
실제로 매크로를 사용하면 추가 언어 나 도구를 배울 필요가 없으며 언어 자체의 모든 기능을 사용하지 않고도 리스프 위에 자체 미니 언어 를 구축 할 수 있습니다.
S-expressions
목록이 아니라로 구성됩니다.
리스프 매크로는 거의 모든 규모의 프로그래밍 프로젝트에서 발생하는 패턴을 나타냅니다. 결국 큰 프로그램에는 소스 코드를 텍스트로 출력하여 붙여 넣을 수있는 프로그램을 작성하는 프로그램을 작성하는 것이 더 간단하고 오류가 적다는 것을 인식하는 특정 코드 섹션이 있습니다.
파이썬에서 객체에는 두 가지 메소드 __repr__
가 __str__
있습니다. __str__
단순히 사람이 읽을 수있는 표현입니다. __repr__
유효한 파이썬 코드 인 표현, 즉 유효한 파이썬으로 해석기에 입력 할 수있는 표현을 리턴합니다. 이렇게하면 실제 소스에 붙여 넣을 수있는 유효한 코드를 생성하는 작은 Python 스 니펫을 만들 수 있습니다.
Lisp에서이 전체 프로세스는 매크로 시스템에 의해 공식화되었습니다. 물론 구문에 대한 확장을 만들고 모든 종류의 멋진 작업을 수행 할 수는 있지만 실제 유용성은 위와 같이 요약됩니다. 물론 Lisp 매크로 시스템을 사용하면 이러한 "스 니펫"을 전체 언어의 모든 기능으로 조작 할 수 있습니다.
간단히 말해서, 매크로는 코드의 변환입니다. 그것들은 많은 새로운 구문 구성을 도입 할 수 있습니다. 예를 들어 C #에서 LINQ를 고려하십시오. 또한 매크로로 구현되는 유사한 언어 확장이 있습니다 (예 : 내장 루프 구문, 반복). 매크로는 코드 중복을 크게 줄입니다. 매크로는«작은 언어»를 포함 할 수있게합니다 (예 : c # / java에서 xml을 사용하여 구성 할 경우 매크로와 동일한 기능을 수행 할 수 있음). 매크로는 라이브러리 사용의 어려움을 숨길 수 있습니다.
예를 들어, lisp에서 당신은 쓸 수 있습니다
(iter (for (id name) in-clsql-query "select id, name from users" on-database *users-database*)
(format t "User with ID of ~A has name ~A.~%" id name))
C #에서는 SqlConnections, SqlCommands를 작성하고 SqlParameters를 SqlCommands에 추가하고 SqlDataReaders를 반복하여 올바르게 닫는 것이 필요합니다.
위의 모든 매크로가 무엇인지 설명하고 멋진 예제가 있지만 매크로와 일반 함수의 주요 차이점은 LISP가 함수를 호출하기 전에 모든 매개 변수를 먼저 평가한다는 것입니다. 매크로의 경우 그 반대가됩니다. LISP는 평가되지 않은 매개 변수를 매크로에 전달합니다. 예를 들어, 함수에 (+ 1 2)를 전달하면 함수는 값 3을받습니다.이 값을 매크로에 전달하면 List (+ 1 2)를받습니다. 이것은 매우 유용한 모든 종류의 작업을 수행하는 데 사용할 수 있습니다.
전달 된 함수를 실행하는 데 걸리는 시간을 측정하십시오. 함수를 사용하면 제어가 함수에 전달되기 전에 매개 변수가 평가됩니다. 매크로를 사용하면 스톱워치의 시작과 중지 사이에 코드를 연결할 수 있습니다. 아래는 매크로와 함수에서 정확히 동일한 코드를 가지며 출력은 매우 다릅니다. 참고 : 이것은 고안된 예이며 차이점을 더 잘 강조하기 위해 구현이 선택되었습니다.
(defmacro working-timer (b)
(let (
(start (get-universal-time))
(result (eval b))) ;; not splicing here to keep stuff simple
((- (get-universal-time) start))))
(defun my-broken-timer (b)
(let (
(start (get-universal-time))
(result (eval b))) ;; doesn't even need eval
((- (get-universal-time) start))))
(working-timer (sleep 10)) => 10
(broken-timer (sleep 10)) => 0
나는 일반적인 lisp 요리 책에서 이것을 얻었지만 lisp 매크로가 좋은 방법으로 좋은 이유를 설명했다고 생각합니다.
"매크로는 다른 Lisp 코드에서 작동하는 Lisp 코드의 일반적인 조각으로,이를 Lisp 실행 파일에 더 가까운 버전으로 변환합니다. 약간 복잡한 것처럼 들릴 수 있으므로 간단한 예를 들어 보겠습니다. 두 변수를 같은 값으로 설정하는 setq 버전입니다.
(setq2 x y (+ z 3))
언제 z=8
x와 y가 모두 11로 설정되어 (이 용도는 생각할 수 없지만 단지 예일뿐입니다.)
setq2를 함수로 정의 할 수 없다는 것은 분명합니다. 경우 x=50
와 y=-5
,이 함수의 값을받을 50, -5, 11; 어떤 변수를 설정해야하는지 알 수 없습니다. 우리가 정말로 말하고 싶은 것은, 당신 (Lisp 시스템)이 볼 때 (setq2 v1 v2 e)
, 그것을 동등하게 취급하는 것 (progn (setq v1 e) (setq v2 e))
입니다. 실제로 이것은 옳지 않지만 지금은 그렇게 될 것입니다. 매크로를 사용하면 입력 패턴을 (setq2 v1 v2 e)
"출력 패턴"으로 변환하기위한 프로그램을 지정하여이를 정확하게 수행 할 수 있습니다 (progn ...)
.
이것이 좋았다고 생각한다면 여기를 계속 읽으십시오 : http://cl-cookbook.sourceforge.net/macros.html
setq2
있다면 함수로서 x
그리고 y
참조에 의해 전달된다. 그러나 CL에서 가능한지 모르겠습니다. 따라서 Lisps 또는 CL을 모르는 사람에게는 특히 예시가 아닙니다. IMO