LaTeX에서 자기만의 명령, 곧 매크로를 정의하는 가장 큰 이유는 타자 수를 줄이는 데 있지 않습니다. 벡터를 모두 \mathbf{v}로 조판한 학위 논문이 있고 그런 자리가 사백 군데인데, 지도 교수가 이제 와서 화살표로 바꾸자고 하는 장면을 떠올려 보십시오. 그 자리에 \vect{v}라고 써 두고 \newcommand 하나로 뒷받침했다면 이 변경은 프리앰블의 한 줄로 끝납니다. 그렇지 않다면 사백 번의 조심스러운 수정입니다. 매크로란 나중에 마음을 바꿀 권리를 보관해 두는 자리입니다. 이 페이지는 \newcommand와 인수 쓰는 법에서 시작해 \renewcommand와 \providecommand의 차이, 매크로 뒤의 공백이 사라지는 이유, 그리고 fragile 명령과 \protect, 현대적인 \NewDocumentCommand까지 차례로 살펴봅니다.
\newcommand로 자기 명령 만들기
문법은 한 줄입니다. \newcommand{\name}{definition} 에서 첫 번째 인수는 만들려는 명령 이름이고 두 번째 인수는 그 내용이며, 이후 \name을 입력할 때마다 definition으로 치환됩니다. 두는 자리는 프리앰블, 즉 \begin{document} 앞이 정석입니다. 앞머리의 벡터 이야기가 힘을 발휘하는 지점이 여기입니다. \vect라는 이름은 모양이 아니라 의미 를 가리킵니다. “여기는 굵게”가 아니라 “이것은 벡터다”라고 써 두면, 굵게 할지 위에 화살표를 올릴지의 판단이 오직 한 곳에만 남습니다. 이것은 LaTeX 자신의 설계와 같은 생각입니다. 본문에 14pt 굵은 글씨를 직접 지정하지 않고 \section이라고 쓰는 것도 똑같은 거래이기 때문입니다.
% preamble: one line decides how every vector in the document looks
\usepackage{amsmath,amssymb}
\newcommand{\vect}[1]{\mathbf{#1}}
% \newcommand{\vect}[1]{\vec{#1}} % swap this line, the whole thesis follows
% semantic names for things you refer to constantly
\newcommand{\R}{\mathbb{R}}
\newcommand{\dd}{\mathrm{d}}
% body
\[ \vect{v} \cdot \vect{w} = \lvert \vect{v} \rvert \, \lvert \vect{w} \rvert \cos\theta \]
\[ \int_{\R} f(x) \, \dd x \]인수를 받는 매크로와 선택 인수
호출할 때마다 내용을 바꾸려면 이름 뒤 대괄호에 인수 개수 를 적고, 정의 안에서 #1, #2 처럼 받습니다. 곧 \newcommand{\name}[⟨nargs⟩]{... #1 #2 ...} 입니다. 여기에는 딱 하나의 상한이 있어서, 매개변수는 #1부터 #9까지 최대 아홉 개 입니다. 재미있는 점은 열 번째를 요구했을 때 나오는 ! You already have nine parameters. 라는 오류가 LaTeX이 아니라 TeX 엔진 자체 에서 나온다는 것입니다. 이 제한은 아래에서 동작하는 \def 프리미티브의 사양이라 \newcommand로는 풀 수 없습니다. 아홉 개로 모자란다면 대개 위치 인수가 아니라 키-값 옵션으로 설계를 바꾸라는 신호입니다.
% two mandatory arguments: a number and a unit
\newcommand{\unit}[2]{#1\,\mathrm{#2}}
$a = \unit{9.8}{m/s^2}$한 걸음 더 나아가 첫 번째 인수만 선택 사항으로 만들고 기본값을 줄 수도 있습니다. 대괄호를 두 번 겹친 \newcommand{\name}[⟨nargs⟩][⟨default⟩]{...} 가 그 형태로, #1이 선택 인수가 되고 생략하면 ⟨default⟩가 들어갑니다. 호출은 \name{...}(기본값 사용) 또는 \name[x]{...}(#1을 x로)이며, 남은 필수 인수는 #2부터 셉니다. 여기서 세는 방식이 함정입니다. [⟨nargs⟩]에는 선택 인수를 포함한 전체 인수 개수 를 적습니다. 아래 예의 [2][2]는 “인수 두 개, 그중 첫 번째가 선택이고 기본값은 2”라는 뜻입니다.
% two arguments in total; the first is optional and defaults to 2
\newcommand{\pow}[2][2]{(x + y)^{#1}_{#2}}
$\pow{n}$ % -> (x + y)^2_n
$\pow[3]{n}$ % -> (x + y)^3_n
% starred form: the argument may not contain a blank line
\newcommand*{\keyword}[1]{\textsf{#1}}자잘한 점 두 가지를 덧붙입니다. 첫째, [⟨default⟩]를 쓰지 않는 것 과 빈 대괄호 []를 쓰는 것은 다릅니다. 후자는 “기본값이 빈 문자열인 선택 인수”가 됩니다. 둘째, 별표가 붙은 \newcommand* 는 인수에 빈 줄(\par)을 넣을 수 없는 “short” 매크로를 만듭니다. 제약처럼 보이지만 실은 진단 장치입니다. 닫는 중괄호를 빠뜨리면 ! Paragraph ended before \keyword was complete. 가 틀린 자리 근처에서 나옵니다. 별표가 없으면 TeX은 다음 문단도, 그다음 문단도 인수의 일부로 읽어 나가고, 오류는 몇 쪽 뒤에서야 드러납니다. 문단을 넘길 이유가 없는 매크로에는 *를 붙여 두는 편이 좋습니다.
\newcommand・\renewcommand・\providecommand의 차이
셋 다 인수를 쓰는 방식은 똑같고, 다른 것은 이미 쓰이고 있는 이름을 만났을 때의 태도 뿐입니다. \newcommand는 거부하고 멈추며, \renewcommand는 덮어쓰고, \providecommand는 조용히 물러나 기존 정의를 남깁니다. 그래서 이미 있는 이름에 \newcommand를 쓰면 ! LaTeX Error: Command \emph already defined. 로 멈추고, 반대로 정의되지 않은 이름에 \renewcommand를 쓰면 ! LaTeX Error: Command \foo undefined. 로 멈춥니다. 이 두 오류는 한 쌍을 이루어, 모르는 사이에 남의 명령을 뭉개는 사고 와 뭉갠 줄 알았는데 아무것도 바뀌지 않은 사고 를 양쪽에서 막아 줍니다.
한 가지 이상한 일이 있습니다. \newcommand{\endnotes}{...} 라고 쓰면 그런 이름의 명령이 어디에도 없는데 Command \endnotes already defined. 로 멈춥니다. 오류의 둘째 줄이 사정을 밝혀 주는데, Or name \end... illegal, see p.192 of the manual. 이라고 이어집니다. latex.ltx 안의 이름 검사는 먼저 그 이름이 정의되지 않았음을 확인한 뒤, 거기에 더해 앞 세 글자가 end가 아닐 것, 그리고 이름이 relax가 아닐 것 을 요구합니다. \end{itemize}는 내부적으로 \enditemize를 부르는 구조여서, end로 시작하는 이름을 마음대로 만들게 두면 환경의 짝이 어긋납니다. 그래서 이 접두사 전체가 예약되어 있습니다. “already defined”라는 문구는 두 경우를 한꺼번에 덮는 뭉뚱그린 표현일 뿐입니다.
| 명령 | 기존 이름에 대해 | 주요 용도 |
|---|---|---|
\newcommand | 오류로 중단 | 새 명령을 안전하게 만들기 |
\renewcommand | 덮어씀(정의되어 있지 않으면 오류) | 기존 명령 다시 만들기 |
\providecommand | 아무것도 하지 않음(기존 정의 유지) | 중복 로드될 수 있는 스타일 파일 |
\DeclareRobustCommand | 덮어쓰고 로그에 기록을 남김 | 이동 인수에서 쓰는 robust 명령 |
실무에서의 역할 분담은 분명합니다. \renewcommand 는 “LaTeX이 이미 마련해 둔 것을 갈아 끼운다”는 창구이며, 목록 기호를 바꾸는 \renewcommand{\labelitemi}{--} 가 대표적입니다. \providecommand 는 “아직 없으면 마련한다”는 선언으로, 자기 스타일 파일이 서로 다른 두 곳에서 두 번 읽혀도 사고가 나지 않게 해 줍니다. 아래 예에서는 \vect가 이미 있으므로 \providecommand는 아무것도 하지 않고 굵은 글씨 정의가 살아남습니다. 그리고 \DeclareRobustCommand 는 다음 절의 주인공입니다. 기존 이름에 써도 멈추지 않고 LaTeX Info: Redefining \emph on input line 2. 같은 한 줄을 .log에 남길 뿐입니다. 조용히 덮어쓰는 대신 기록을 남기는 것, 이것이 \renewcommand와의 실질적인 차이입니다.
% replace something the class already defines
\renewcommand{\labelitemi}{--}
% define only if nobody else did; here \vect exists, so this line is a no-op
\providecommand{\vect}[1]{\vec{#1}}
% redefine on purpose, and say so in the log
\DeclareRobustCommand{\emph}[1]{\textbf{#1}}매크로 뒤의 공백이 사라지는 이유와 \xspace
영문자만으로 이루어진 명령 이름은 첫 번째 비영문자에서 끝나며, 그 뒤에 오는 공백은 이름의 끝을 알리는 표시로 삼켜집니다. 그래서 프리앰블에 \newcommand{\lab}{Knuth Lab} 을 두고 본문에 \lab was founded. 라고 쓰면 “Knuth Labwas founded.”로 조판됩니다. 공백이 사라진 것이 아니라, TeX이 \lab이라는 이름을 어디서 끊을지 판단하느라 먹어 버린 것입니다. 흥미로운 것은 예외가 있다는 점입니다. \$ de 는 공백을 그대로 지켜 “$ de”로 나옵니다. \$ 는 비영문자 한 글자로 된 제어 기호라 이름이 그 한 글자에서 완결되고, 뒤를 미리 읽을 이유가 없기 때문입니다. 즉 이 함정은 영문자로 쓴 명령 이름에만 적용됩니다.
\usepackage{xspace}
\newcommand{\lab}{Knuth Lab}
\newcommand{\labx}{Knuth Lab\xspace}
\lab was founded. % -> Knuth Labwas founded.
\lab{} was founded. % -> Knuth Lab was founded.
\lab\ was founded. % -> Knuth Lab was founded.
\labx was founded. % -> Knuth Lab was founded.
\labx, and a comma. % -> Knuth Lab, and a comma.고치는 방법은 셋입니다. 정석은 빈 중괄호 \lab{} 로 이름의 끝을 밝히는 것이고, 다음은 제어 공백 \lab\ , 셋째는 xspace 패키지의 \xspace 입니다. \xspace의 영리한 점은 무조건 공백을 넣지 않고 다음 토큰을 먼저 살펴본 뒤 결정한다 는 데 있습니다. xspace.sty의 예외 목록에는 , . ' / ? ; : ! ~ - ) 와 닫는 중괄호, 그리고 \footnote 같은 것들이 들어 있고, 이들이 뒤따르면 공백을 넣지 않습니다. 그래서 \labx, and 는 “Knuth Lab, and”로 바르게 조판됩니다. 이 패키지는 LaTeX tools 번들의 일부이며 원저자는 David Carlisle입니다. 필요하면 \xspaceaddexceptions 로 목록을 늘릴 수 있습니다. 다만 대가도 있습니다. \xspace는 앞을 미리 읽는 기법이라 인수를 받는 매크로에는 필요 없고(어차피 }로 끝납니다), 다른 매크로의 인수 안에서는 뜻밖의 결과를 낼 때가 있습니다. 헷갈리면 셋 중 {}가 가장 안전합니다.
fragile 명령, 이동 인수, \protect와 \DeclareRobustCommand
직접 만든 매크로가 절 제목이나 캡션 안에서 갑자기 깨질 때가 있습니다. 원인은 이동 인수(moving argument) 입니다. \section{...} 의 글자는 본문에 조판될 뿐 아니라 목차를 위해 .aux 보조 파일로 쓰이고 페이지 머리글로도 넘어갑니다. 같은 내용이 다른 곳으로 “이동”하는 것입니다. \caption{...}, \thanks{...}, 그리고 tabular와 array의 @{...} 도 같은 성질을 가집니다. 밖으로 쓰이는 순간에 전개되면 의미를 잃는 코드를 담은 명령을 fragile 명령, 그대로 쓰여도 멀쩡한 것을 robust 명령 이라고 부릅니다.
고전적인 대처는 \protect 입니다. fragile 명령 바로 앞에 두어 “여기서는 전개하지 말고 그대로 써 내보내라”고 지시합니다. 한 번에 명령 하나만 보호합니다. 다만 좋은 소식이 있습니다. 2019년 10월 LaTeX 릴리스 이후, 예전에 fragile이던 명령 다수가 robust로 바뀌었습니다. 이 변경은 LaTeX News 30의 “Making more user commands robust” 절에 기록되어 있고, \begin과 \end까지 다뤄서 이제는 환경 전체를 제목 안에 쓸 수 있습니다. 끝까지 남은 대표 주자는 \verb 입니다. 절 제목에 넣으면 ! LaTeX Error: \verb illegal in argument. 로 멈춥니다(대개 ! Paragraph ended before \@sect was complete. 도 함께 나옵니다). 이것만은 \protect로도 구할 수 없으니, 제목이나 캡션에서는 \texttt{...} 로 고쳐 쓰는 편이 현실적입니다.
% \verb cannot go here at all -- rewrite it
\section{The \texttt{\textbackslash par} primitive}
% a macro that is robust from the start, even though \ifmmode is fragile
\DeclareRobustCommand{\seq}[2][n]{%
\ifmmode #2_{1}\ldots #2_{#1}\else\textbf{??}\fi
}
\section{Sequences $\seq{x}$} % works without \protect직접 만든 매크로라면 매번 \protect를 떠올리기보다 \DeclareRobustCommand 로 처음부터 robust하게 정의해 두는 편이 확실합니다. 인수 쓰는 방식은 \newcommand와 똑같고, 본문에 \ifmmode 같은 fragile 코드가 섞여 있어도 완성된 명령은 이동 인수를 견딥니다. 위의 \seq는 LaTeX 자체 문서 clsguide에 실린 예로, 바로 이 점을 보이려고 쓰인 것입니다. 대가는 아주 약간의 비효율이므로, 제목이나 캡션에 나올 일이 없는 매크로까지 robust로 만들 필요는 없습니다. 판단 기준은 “이 매크로가 목차에 실릴 가능성이 있는가” 하나뿐입니다.
\NewDocumentCommand — 현대적인 정의 방식
\newcommand는 한 가지 모양밖에 만들지 못합니다. 대괄호 선택 인수를 최대 하나, 그 뒤에 필수 인수입니다. \NewDocumentCommand{\name}{⟨arg-spec⟩}{...} 는 이 천장을 걷어냅니다. 인수 개수를 숫자로 주는 대신, 인수의 종류를 나타내는 글자를 늘어놓은 “인수 지정(arg-spec)” 을 건넵니다. 원래는 xparse 패키지의 기능이었지만 2020년 10월 1일 릴리스에서 LaTeX 커널(ltcmd 모듈)로 들어와, 지금은 \usepackage{xparse} 없이 쓸 수 있습니다. 이 이관은 LaTeX News 32에 기록되어 있습니다.
| 지정자 | 의미 | 정의 안에서 받는 형태 |
|---|---|---|
m | 필수 인수(mandatory) | 보통의 #1 등 |
o | 선택적 [...] 인수 | 없으면 “값 없음” 표시 |
O{default} | 기본값이 있는 선택 인수 | 없으면 default가 들어감 |
s | 별표 *의 유무 | \IfBooleanTF로 판정 |
\newcommand에 대해 결정적으로 앞서는 지점이 여기입니다. 선택 인수를 여러 개 받을 수 있고, 별표(starred) 변종을 정식 기능으로 다룹니다. s를 적어 두면 #1에 별표 유무가 참거짓 값으로 들어오고, \IfBooleanTF{#1}{별표 있음}{별표 없음} 으로 분기합니다. 이름 앞의 New・Renew・Provide・Declare 를 바꿔 끼우면 각각 \newcommand・\renewcommand・\providecommand・무조건 덮어쓰기에 대응합니다. 새로 쓰는 코드에서는 이 인터페이스를 기본으로 삼아도 좋습니다. 다만 \newcommand가 사라지거나 낡은 것은 아니어서, 인수가 한둘뿐인 짧은 정의라면 종래대로도 충분합니다.
% s = optional star, m = mandatory argument
\NewDocumentCommand{\diff}{s m}{%
\IfBooleanTF{#1}%
{\frac{\mathrm{d}}{\mathrm{d}#2}}% starred: d/dx
{\mathrm{d}#2}% plain: dx
}
$\diff{x}$ % -> dx
$\diff*{x}$ % -> d/dx
% O{...} gives an optional argument with a default
\NewDocumentCommand{\note}{O{note} m}{\textbf{#1:} #2}충돌하지 않는 매크로 이름 짓기와 정의를 둘 자리
이름 충돌을 피하는 최고의 도구는 사실 \newcommand 자신입니다. \renewcommand나 \def로 먼저 덮어써 버리면 무엇을 부쉈는지 알 길이 없지만, \newcommand로 먼저 정의해 보면 already defined 가 나오는 순간 “그 이름은 누군가 쓰고 있다”고 알려 줍니다. 그렇기 때문에 커널이나 패키지 명령을 \renewcommand로 가볍게 뭉개지 말아야 합니다. 직접 짓는 이름은 너무 짧은 것을 피하고 프로젝트 고유의 접두사 를 붙이는 편이 안전합니다(\myR, \bookTitle 등). 특히 짧은 수학 연산자 이름은 이미 쓰이고 있습니다. \ker, \deg, \arg, \Re 가 그렇습니다. 그러니 \R을 쓰고 싶다면 \newcommand로 한 번 시험해 볼 값어치가 있습니다.
매크로가 너무 많아도 읽기 어려워집니다. \newcommand{\x}{\xi} 같은 극단적인 축약은 몇 달 뒤의 자신과 공저자에게 암호입니다. 반복이 잦고, 나중에 한꺼번에 바꿀 법하고, 의미에 이름을 붙일 값어치가 있는 것에 한정하고 나머지는 그대로 쓰는 편이 대개 더 잘 읽힙니다. 판정은 간단합니다. 이 이름이 보자마자 이해되는가? \vect는 되고 \x는 되지 않습니다.
마지막은 둘 자리입니다. 논문 한 편이라면 프리앰블로 충분하지만, 장별로 파일을 나눈 책이나 같은 표기를 돌려 쓰는 여러 편의 논문이라면 정의만 따로 파일로 떼어 \usepackage로 읽어 들이는 편이 관리하기 쉽습니다. 여기에 작은 이점이 하나 있습니다. .sty나 .cls 안에서는 @가 영문자로 취급되므로, \makeatletter를 쓰지 않고도 \mybook@vecfont 같은 내부 이름을 그대로 쓸 수 있습니다. @가 든 이름은 문서 본문에서 부를 수 없으니, 밖으로 공개하는 명령과 내부 전용 명령을 이름만으로 구분 할 수 있습니다. 프리앰블에서 같은 일을 하려면 \makeatletter와 \makeatother로 감싸야 하고, 그만큼 실수할 여지가 늘어납니다.
% ---- mynotation.sty --------------------------------------------
\ProvidesPackage{mynotation}[2024/01/01 shared notation]
\RequirePackage{amsmath,amssymb}
% private: the @ makes it uncallable from the document body
\newcommand{\mynot@vecfont}[1]{\mathbf{#1}}
% public
\newcommand{\vect}[1]{\mynot@vecfont{#1}}
\newcommand{\R}{\mathbb{R}}
% ---- thesis.tex ------------------------------------------------
% \usepackage{mynotation}