Lisp 매크로는 얼마나 유용합니까?


22

Common Lisp을 사용하면 원하는 소스 변환을 수행하는 매크로를 작성할 수 있습니다.

구성표는 변환을 수행 할 수있는 위생적인 ​​패턴 일치 시스템을 제공합니다. 실제로 매크로는 얼마나 유용합니까? Paul Graham은 Beating the Averages 에서 다음과 같이 말했습니다 .

Viaweb 편집기의 소스 코드는 약 20-25 % 매크로 일 것입니다.

사람들이 실제로 매크로로하는 일은 무엇입니까?


나는 이것이 분명히 좋은 주관적 이라고 생각합니다 . 형식화를 위해 귀하의 질문을 편집했습니다. 이 중복 될,하지만 난 하나를 찾을 수 없습니다.
Tim Post

1
기능에 맞지 않는 것처럼 보이는 모든 것은 반복적이라고 생각합니다.

2
당신은에 리스프를 설정하는 매크로를 사용할 수 있는 : 구문 및 의미와 함께, 다른 언어 bit.ly/vqqvHU
SK-논리

programmers.stackexchange.com/questions/81202/… 여기에서 살펴볼 가치가 있지만 중복 된 것은 아닙니다.
David Thornley

답변:


15

Matthias Felleisen이 2002 년 LL1 토론 목록에 올린이 게시물 을 살펴보십시오 . 그는 매크로의 세 가지 주요 용도를 제안합니다.

  1. 데이터 하위 언어 : 간단한 표정을 작성하고 매크로로 깔끔하게 꾸며진 따옴표, 따옴표 등으로 복잡한 중첩 목록 / 배열 / 테이블을 만들 수 있습니다.
  2. 바인딩 구성 : 매크로를 사용하여 새로운 바인딩 구성을 도입 할 수 있습니다. 이를 통해 람다를 제거하고 함께 속한 것들을 더 가깝게 배치 할 수 있습니다.
  3. 평가 순서 변경 : 필요에 따라 표현식의 평가를 지연 / 연기하는 구문을 소개 할 수 있습니다. 루프, 새로운 조건, 지연 / 강제 등을 생각하십시오. [참고 : Haskell 또는 다른 게으른 언어에서는이 언어가 필요하지 않습니다.]

18

나는 주로 시간을 절약하는 새로운 언어 구조를 추가하기 위해 매크로를 사용합니다. 그렇지 않으면 많은 상용구 코드가 필요합니다.

예를 들어, 최근 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 줄의 코드로 된 새로운 범용 컴파일 타임 언어 구조가 있습니다.


13

사람들이 실제로 매크로로하는 일은 무엇입니까?

언어 확장 또는 DSL 작성

리스프와 같은 언어로 이것을 느끼려면, Racket을 공부하십시오 . 여기에는 Typed Racket, R6RS 및 Datalog와 같은 여러 언어 변형이 있습니다.

매크로를 통해 도메인 별 언어를 만들기위한 특정 목적을 위해 컴파일러 파이프 라인에 액세스 할 수 있는 Boo 언어 도 참조하십시오 .


4

여기 몇 가지 예가 있어요.

계획:

  • define함수 정의를 위해. 기본적으로 함수를 정의하는 더 짧은 방법을 만듭니다.
  • let 어휘 범위 변수를 작성합니다.

클로저 :

  • defn문서에 따르면 :

    var 메타 데이터에 추가 된 doc-string 또는 attr이있는 (def name (fn [params *] exprs *)) 또는 (def name (fn ([params *] exprs *) +))와 동일

  • for: 목록 이해
  • defmacro아이러니?
  • defmethod, defmulti: 다중 메소드 작업
  • ns

이러한 매크로를 많이 사용하면보다 추상적 인 수준에서 코드를 훨씬 쉽게 작성할 수 있습니다. 매크로는 여러 가지면에서 리스프가 아닌 구문과 비슷하다고 생각합니다.

플로팅 라이브러리 Incanter 는 복잡한 데이터 조작을위한 매크로를 제공합니다.


4

매크로는 일부 패턴을 포함하는 데 유용합니다.

예를 들어, 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에는 독자가 코드를 해석하는 방식을 수정하는 데 사용할 수있는 독자 매크로라는 또 다른 종류의 매크로 가 있습니다. 즉, # {를 사용하기 위해 이들을 사용할 수 있습니다.


3

다음은 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 / 데이터 하위 언어 부분입니다.


1
함수 적용은 스레딩보다 훨씬 더 의미가 있지만 반드시 습관의 문제입니다. 좋은 대답입니다.
코어 덤프
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.