프로그램에서 매크로를 언제 사용해야합니까?
이 질문은 @tarsius 의 유익한 답변에서 영감을 얻었습니다 . 많은 사람들이 다른 사람들과 공유 할 수있는 훌륭한 경험이 있다고 생각합니다. 이 질문이 위대한 주관적인 질문 중 하나가되어 Emacs 프로그래머에게 도움 이되기를 바랍니다 .
프로그램에서 매크로를 언제 사용해야합니까?
이 질문은 @tarsius 의 유익한 답변에서 영감을 얻었습니다 . 많은 사람들이 다른 사람들과 공유 할 수있는 훌륭한 경험이 있다고 생각합니다. 이 질문이 위대한 주관적인 질문 중 하나가되어 Emacs 프로그래머에게 도움 이되기를 바랍니다 .
답변:
구문 에는 매크로를 사용 하지만 의미 에는 사용 하지 마십시오 .
일부 구문 설탕에는 매크로를 사용하는 것이 좋지만 매크로 자체 또는 확장에 상관없이 매크로 본문에 논리를 넣는 것은 좋지 않습니다. 그렇게해야한다고 생각되면 대신 함수를 작성하고 구문 설탕에 대한 "컴패니언 매크로"를 작성하십시오. 간단히 말해서, let
매크로에 들어 있으면 문제가 있습니다.
매크로에는 많은 단점이 있습니다.
필자의 경험과 다른 사람의 Elisp 코드를 읽고, 디버깅하고, 수정하면서 가능한 한 매크로를 피하는 것이 좋습니다.
매크로에 대한 분명한 점은 인수를 평가하지 않는다는 것입니다. 즉, 매크로에 래핑 된 복잡한 표현식이있는 경우 매크로 정의를 찾지 않으면 해당 표현식이 평가되는지 여부와 그 컨텍스트를 알 수 없습니다. 자신의 매크로에 대한 정의를 알고 있기 때문에 (현재는 몇 년이 지나지 않을 것이므로) 이것은 큰 문제가되지 않을 수 있습니다.
이 단점은 함수에는 적용되지 않습니다. 모든 인수는 함수 호출 전에 평가됩니다. 이는 알려지지 않은 디버깅 상황에서 평가 복잡성을 구축 할 수 있음을 의미합니다. 간단한 데이터를 반환하고 전역 상태를 건드리지 않는다고 가정하는 한 알려지지 않은 함수의 정의를 바로 건너 뛸 수도 있습니다.
다시 말하면, 매크로는 언어의 일부가됩니다. 알 수없는 매크로가있는 코드를 읽는 경우, 모르는 언어로 된 코드를 읽는 것입니다.
표준 매크로 좋아 push
, pop
, dolist
당신을 위해 정말 많은 작업을 수행하고, 다른 프로그래머에게 잘 알려져있다. 기본적으로 언어 사양의 일부입니다. 그러나 자체 매크로를 표준화하는 것은 사실상 불가능합니다. 위의 예제와 같이 간단하고 유용한 것을 생각해 내기가 어려울뿐만 아니라 모든 프로그래머가 그것을 배우도록 설득하는 것이 더 어렵습니다.
또한, 나는 매크로를 신경 쓰지 않습니다 save-selected-window
: 그들은 상태를 저장하고 복원하고 같은 방식으로 인수를 평가합니다 progn
. 매우 간단하여 실제로 코드를 더 단순하게 만듭니다.
OK 매크로의 또 다른 예는 defhydra
또는 과 같은 것일 수 있습니다 use-package
. 이 매크로는 기본적으로 자체 미니 언어와 함께 제공됩니다. 그리고 그들은 여전히 간단한 데이터를 취급하거나 다음과 같이 행동 progn
합니다. 또한 이들은 일반적으로 최상위 수준이므로 매크로 인수에 의존하는 호출 스택에는 변수가 없으므로 조금 더 간단합니다.
나쁜 매크로의 예는, 내 의견에서, magit-section-action
그리고 magit-section-case
Magit있다. 첫 번째 것은 이미 제거되었지만 두 번째는 여전히 남아 있습니다.
나는 무뚝뚝해질 것이다. "시맨틱이 아닌 구문을위한 사용"이 무엇을 의미하는지 이해하지 못한다. 이것이이 답변의 일부가 lunaryon 의 답변을 다루는 이유 이고, 다른 부분은 원래 질문에 대답하려는 나의 시도 일 것입니다.
프로그래밍에서 "의미"라는 단어가 사용될 때, 언어 작성자가 언어를 해석하기로 선택한 방식을 나타냅니다. 이것은 일반적으로 언어의 문법 규칙을 독자가 잘 알고있는 다른 규칙으로 변환하는 일련의 공식으로 요약됩니다. 예를 들어, Plotkin은 자신의 저서 " 작업 의미론에 대한 구조적 접근 방식"에서 논리 파생 언어를 사용하여 추상 구문이 평가하는 내용 (프로그램 실행의 의미)을 설명합니다. 다른 말로하면, 문법이 어떤 수준에서 프로그래밍 언어를 구성하는 것이라면, 그것은 의미론을 가져야 만한다.
그러나 공식적인 정의를 잊어 버리십시오. 결국 의도를 이해하는 것이 중요합니다. 따라서 lunaryon은 프로그램에 새로운 의미가 추가되지 않고 일종의 약어 인 경우 독자가 매크로를 사용하도록 권장하는 것처럼 보입니다. 나에게 이것은 매크로가 실제로 어떻게 사용되는지와는 이상하게 들린다. 다음은 새로운 의미를 명확하게 만드는 예입니다.
defun
언어의 필수 부분 인 친구는 함수 호출을 설명하는 것과 같은 용어로 설명 할 수 없습니다.
setf
함수의 작동 방식을 설명하는 규칙은 식의 효과를 설명하기에 부적합합니다 (setf (aref x y) z)
.
with-output-to-string
또한 매크로 내부의 코드 의미를 변경합니다. 마찬가지로 with-current-buffer
다른 with-
매크로도 많이 있습니다.
dotimes
기능 측면에서 유사한 것은 설명 될 수 없다.
ignore-errors
랩핑하는 코드의 의미를 변경합니다.
Emacs Lisp에서 사용되는 cl-lib
패키지, eieio
패키지 및 기타 거의 모든 유비쿼터스 라이브러리 에는 매크로가 많이 포함되어 있지만 함수 형식은 다른 해석을하는 것처럼 보일 수 있습니다.
따라서 매크로는 새로운 의미를 언어에 도입하는 도구입니다. 나는 그것을하는 다른 방법을 생각할 수 없습니다 (적어도 Emacs Lisp에서는 아닙니다).
매크로를 사용하지 않을 때 :
그들이 새로운 의미를 가진 새로운 구조를 도입하지 않을 때 (즉, 함수가 그처럼 잘 작동 할 것입니다).
이것이 일종의 편의 일 때 (즉 , 더 짧게 만들기 위해 mvb
확장되는 이름의 매크로 cl-multiple-value-bind
를 만드는 것).
많은 오류 처리 코드가 매크로에 의해 숨겨 질 것으로 예상 할 때 (알려진 바와 같이, 매크로는 디버그하기 어렵습니다).
함수보다 매크로를 강력하게 선호하는 경우 :
함수 호출에 의해 도메인 특정 개념이 가려 질 때 예를 들어, 동일한 context
인수를 연속해서 호출해야하는 여러 함수에 전달해야하는 경우 context
인수가 숨겨진 매크로로 랩핑하는 것이 좋습니다.
함수 형태의 일반 코드가 지나치게 장황한 경우 (Emacs Lisp의 베어 반복 구조는 너무 장황하고 프로그래머가 저수준 구현 세부 사항에 불필요하게 노출시킵니다).
아래는 컴퓨터 과학에서 의미론이 의미하는 바에 대한 직관을 제공하기위한 요약본입니다.
다음 구문 규칙만으로 매우 간단한 프로그래밍 언어가 있다고 가정하십시오.
variable := 1 | 0
operation := +
expression := variable | (expression operation expression)
이 언어와 우리는 같은 "프로그램"만들 수 있습니다 (1+0)+1
또는 0
또는 ((0+0))
등
그러나 시맨틱 규칙을 제공하지 않는 한 이러한 "프로그램"은 의미가 없습니다.
이제 우리의 언어에 (의미 적) 규칙이 있다고 가정 해 봅시다.
0+0 0+1 1+0 1+1 (exp)
---, ---, ---, ---, -----
0 1+0 1 0 exp
이제이 언어를 사용하여 실제로 계산할 수 있습니다. 즉, 우리는 구문 표현을 취하여 추상적으로 이해하고 (즉, 추상적 구문으로 변환) 의미 론적 규칙을 사용하여이 표현을 조작 할 수 있습니다.
Lisp 계열 언어에 대해 이야기 할 때, 함수 평가의 기본 의미 론적 규칙은 대략 람다 계산법의 규칙입니다.
(f x) (lambda x y) x
-----, ------------, ---
fx ^x.y x
인용 및 매크로 확장 메커니즘은 메타 프로그래밍 도구라고도합니다. 즉 , 프로그래밍 대신 프로그램 에 대해 이야기 할 수 있습니다. 그들은 "메타 레이어"를 사용하여 새로운 의미를 만들어서 이것을 달성합니다. 이러한 간단한 매크로의 예는 다음과 같습니다.
(a . b)
-------
(cons a b)
이것은 실제로 Emacs Lisp의 매크로는 아니지만 가능했을 수도 있습니다. 나는 단순성을 위해서만 선택했다. 가장 가까운 후보 가 함수로 (f x)
해석 f
되지만 a
반드시 함수일 필요는 없으므로 위에 정의 된 의미 규칙은이 경우에 적용 되지 않습니다.
(x y)
은 대략 "y를 평가 한 다음 이전 평가의 결과로 x를 호출합니다"입니다. 을 쓸 때 (defun x y)
y는 평가되지 않으며 x도 아닙니다. 다른 의미를 사용하여 동등한 효과를 가진 코드를 작성할 수 있는지 여부는 자체 의미가있는이 코드 조각을 거부하지 않습니다.
(defun x y)
함수 대 애플리케이션 을 해석하기위한 규칙입니다 .