프로그래밍 언어에 대한 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로 끌어들이는 것입니다.
답변
여기 에서 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%")
그리고 그것도 리더 매크로에 들어 가지 않습니다 .
도움이 되었기를 바랍니다.
답변
나는이 동료보다 Lisp 매크로가 더 잘 설명 된 것을 본 적이 없다고 생각한다 : http://www.defmacro.org/ramblings/lisp.html
답변
매크로 및 템플릿을 사용하여 C 또는 C ++에서 수행 할 수있는 작업을 생각해보십시오. 반복 코드를 관리하는 데 매우 유용한 도구이지만 매우 심각한 방식으로 제한됩니다.
- 제한된 매크로 / 템플릿 구문은 사용을 제한합니다. 예를 들어 클래스 나 함수 이외의 것으로 확장되는 템플릿을 작성할 수 없습니다. 매크로와 템플릿은 내부 데이터를 쉽게 유지할 수 없습니다.
- C 및 C ++의 복잡하고 매우 불규칙적 인 구문은 매우 일반적인 매크로를 작성하는 것을 어렵게합니다.
Lisp 및 Lisp 매크로는 이러한 문제를 해결합니다.
- Lisp 매크로는 Lisp로 작성됩니다. 매크로를 작성할 수있는 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();
}
