Common Lisp을 사용하면 원하는 소스 변환을 수행하는 매크로를 작성할 수 있습니다.
구성표는 변환을 수행 할 수있는 위생적인 패턴 일치 시스템을 제공합니다. 실제로 매크로는 얼마나 유용합니까? Paul Graham은 Beating the Averages 에서 다음과 같이 말했습니다 .
Viaweb 편집기의 소스 코드는 약 20-25 % 매크로 일 것입니다.
사람들이 실제로 매크로로하는 일은 무엇입니까?
Common Lisp을 사용하면 원하는 소스 변환을 수행하는 매크로를 작성할 수 있습니다.
구성표는 변환을 수행 할 수있는 위생적인 패턴 일치 시스템을 제공합니다. 실제로 매크로는 얼마나 유용합니까? Paul Graham은 Beating the Averages 에서 다음과 같이 말했습니다 .
Viaweb 편집기의 소스 코드는 약 20-25 % 매크로 일 것입니다.
사람들이 실제로 매크로로하는 일은 무엇입니까?
답변:
Matthias Felleisen이 2002 년 LL1 토론 목록에 올린이 게시물 을 살펴보십시오 . 그는 매크로의 세 가지 주요 용도를 제안합니다.
- 데이터 하위 언어 : 간단한 표정을 작성하고 매크로로 깔끔하게 꾸며진 따옴표, 따옴표 등으로 복잡한 중첩 목록 / 배열 / 테이블을 만들 수 있습니다.
- 바인딩 구성 : 매크로를 사용하여 새로운 바인딩 구성을 도입 할 수 있습니다. 이를 통해 람다를 제거하고 함께 속한 것들을 더 가깝게 배치 할 수 있습니다.
- 평가 순서 변경 : 필요에 따라 표현식의 평가를 지연 / 연기하는 구문을 소개 할 수 있습니다. 루프, 새로운 조건, 지연 / 강제 등을 생각하십시오. [참고 : Haskell 또는 다른 게으른 언어에서는이 언어가 필요하지 않습니다.]
나는 주로 시간을 절약하는 새로운 언어 구조를 추가하기 위해 매크로를 사용합니다. 그렇지 않으면 많은 상용구 코드가 필요합니다.
예를 들어, 최근 for-loop
에 C ++ / Java와 유사한 명령을 원한다는 것을 알았습니다 . 그러나 기능적 언어 인 Clojure에는 기본 언어가 포함되지 않았습니다. 그래서 방금 매크로로 구현했습니다.
(defmacro for-loop [[sym init check change :as params] & steps]
`(loop [~sym ~init value# nil]
(if ~check
(let [new-value# (do ~@steps)]
(recur ~change new-value#))
value#)))
그리고 지금 할 수 있습니다 :
(for-loop [i 0 , (< i 10) , (inc i)]
(println i))
그리고 거기에는 6 줄의 코드로 된 새로운 범용 컴파일 타임 언어 구조가 있습니다.
여기 몇 가지 예가 있어요.
계획:
define
함수 정의를 위해. 기본적으로 함수를 정의하는 더 짧은 방법을 만듭니다.let
어휘 범위 변수를 작성합니다. 클로저 :
defn
문서에 따르면 :
var 메타 데이터에 추가 된 doc-string 또는 attr이있는 (def name (fn [params *] exprs *)) 또는 (def name (fn ([params *] exprs *) +))와 동일
for
: 목록 이해defmacro
아이러니?defmethod
, defmulti
: 다중 메소드 작업ns
이러한 매크로를 많이 사용하면보다 추상적 인 수준에서 코드를 훨씬 쉽게 작성할 수 있습니다. 매크로는 여러 가지면에서 리스프가 아닌 구문과 비슷하다고 생각합니다.
플로팅 라이브러리 Incanter 는 복잡한 데이터 조작을위한 매크로를 제공합니다.
매크로는 일부 패턴을 포함하는 데 유용합니다.
예를 들어, Common Lisp는 while
루프를 정의하지 않지만이를 정의하는 do
데 사용할 수있는 루프 를 정의합니다.
다음은 On Lisp 의 예입니다 .
(defmacro while (test &body body)
`(do ()
((not ,test))
,@body))
(let ((a 0))
(while (< a 10)
(princ (incf a))))
"12345678910"이 인쇄되며 다음 작업이 어떻게 수행되는지 확인하십시오
macroexpand-1
.
(macroexpand-1 '(while (< a 10) (princ (incf a))))
이것은 다음을 반환합니다 :
(DO () ((NOT (< A 10))) (PRINC (INCF A)))
이것은 간단한 매크로이지만 이전에 말했듯이 일반적으로 새로운 언어 또는 DSL을 정의하는 데 사용되지만이 간단한 예에서 이미 수행 할 수있는 작업을 상상할 수 있습니다.
loop
매크로는 매크로가 무엇을 할 수 있는지의 좋은 예입니다.
(loop for i from 0 to 10
if (and (= (mod i 2) 0) i)
collect it)
=> (0 2 4 6 8 10)
(loop for i downfrom 10 to 0
with a = 2
collect (* a i))
=> (20 18 16 14 12 10 8 6 4 2 0)
Common Lisp에는 독자가 코드를 해석하는 방식을 수정하는 데 사용할 수있는 독자 매크로라는 또 다른 종류의 매크로 가 있습니다. 즉, # {를 사용하기 위해 이들을 사용할 수 있습니다.
다음은 Clojure에서 디버깅에 사용하는 것입니다.
user=> (defmacro print-var [varname] `(println ~(name varname) "=" ~varname))
#'user/print-var
=> (def x (reduce * [1 2 3 4 5]))
#'user/x
=> (print-var x)
x = 120
nil
C ++에서 수동 롤 해시 테이블을 처리해야했습니다.이 get
메소드는 상수가 아닌 문자열 참조를 인수로 사용하여 리터럴로 호출 할 수 없습니다. 다루기 쉽도록 다음과 같은 내용을 작성했습니다.
#define LET(name, value, body) \
do { \
string name(value); \
body; \
assert(name == value); \
} while (false)
이 문제와 같은 것이 lisp에 등장하지는 않지만, 예를 들어 실제 let-binding 을 도입하여 인수를 두 번 평가하지 않는 매크로를 가질 수 있다는 것이 특히 좋습니다 . (여기서 나는 그 주위를 둘러 볼 수 있었다).
나는 또한 do ... while (false)
당신이 if의 그때 부분에서 그것을 사용할 수 있고 예상대로 다른 부분의 작업을 할 수 있도록 물건을 포장하는 끔찍한 추악한 해킹에 의지합니다 . lisp에서는 이것을 필요로하지 않습니다.이 구문 분석은 문자열 (또는 토큰 시퀀스, C 및 C ++의 경우)이 아닌 구문 트리에서 작동하는 매크로의 함수입니다.
코드를보다 깔끔하게 읽을 수 있도록 코드를 재구성하는 데 사용할 수있는 내장 스레딩 매크로가 몇 가지 있습니다 (병렬이 아니라 '코드를 함께 파종'하는 것처럼 '스레딩'). 예를 들면 다음과 같습니다.
(->> (range 6) (filter even?) (map inc) (reduce *))
첫 번째 형식을 취하여 (range 6)
다음 형식의 마지막 인수로 만듭니다.이 다음 형식 (filter even?)
의 마지막 인수 등으로 위의 내용이 다시 작성됩니다.
(reduce * (map inc (filter even? (range 6))))
나는 첫 번째 내용이 훨씬 더 명확하게 읽었다 고 생각한다. "이러한 데이터를 가져 와서 수행 한 다음, 그렇게하고 다른 것을하면 우리는 끝났다". 객관적으로 사실은 수행하는 순서대로 작업을 읽는 것입니다 (게으름 무시).
이전 형식을 첫 번째 인수가 아닌 첫 번째 인수로 삽입하는 변형도 있습니다. 하나의 사용 사례는 산술입니다.
(-> 17 (- 2) (/ 3))
"17을 빼고 2를 빼고 3으로 나눕니다"라고 읽습니다.
산술에 관해서는, 표기법 파싱을 삽입하는 매크로를 작성할 수 있으므로, 예를 들어 말을 할 수 없으며, 읽을 수있는 단점과 유효한 lisp 표현의 이점이있는 (infix (17 - 2) / 3)
침을 뱉을 (/ (- 17 2) 3)
수 있습니다. 이것이 DSL / 데이터 하위 언어 부분입니다.