我对 LaTeX 命令有一点疑问,尽管搜索过,但我还是没有找到任何相关信息。我们可以通过简单地将一个命令写入另一个命令中来轻松地管道化命令。例如,如果\a
采用一个参数,我们可以简单地通过以下方式链接\a
并\b
执行:
\a{\b{...}}
其中的点是传递给的参数\b
。
现在,我们假设\a
有 n 个参数,并且\b
会产生一个结果,该结果可以分为 n 个由\b1
, ...,\b
命令计算的输出。我们希望组合这两个命令,就像我们在 n = 1 的简单情况下所做的那样。我天真地认为,\b
按照抽象方案进行定义\newcommand{\b}[N]{{\b1{...}}...{\bn{...}}}
就可以完成工作,就像从形式角度来看一样。
但是,这似乎不起作用,因为以下 n = N = 2 的示例没有提供所需的输出:
\documentclass{article}
\newcommand{\tunnel}[2]{{#1}{#2}}
\newcommand{\name}[2]{\textbf{#1}~\textit{#2}}
\begin{document}
Desired output is \name{hello}{you}
Actual output is \name{\tunnel{hello}{you}}
\end{document}
使用该命令进行编译时xelatex test.tex
,我得到结果
This is XeTeX, Version 3.14159265-2.6-0.999991 (TeX Live 2019/Arch Linux) (preloaded format=xelatex)
restricted \write18 enabled.
entering extended mode
(./test.tex
LaTeX2e <2019-10-01> patch level 1
(/usr/share/texmf-dist/tex/latex/base/article.cls
Document Class: article 2019/08/27 v1.4j Standard LaTeX document class
(/usr/share/texmf-dist/tex/latex/base/size10.clo)) (./test.aux)
Runaway argument?
{
! Paragraph ended before \text@command was complete.
<to be read again>
\par
l.11
? ! Too many }'s.
<recently read> \egroup
l.11
? [1] (./test.aux) )
Output written on test.pdf (1 page).
Transcript written on test.log.
生成的pdf有以下内容:
您知道如何定义\b
(或\tunnel
在示例中)使其以这种方式工作吗?我知道这可能看起来不太优雅,但对于更复杂的文档,我确实需要这种调整。提前谢谢您!
答案1
正如您所知,\tunnel
只需一步即可扩展为两个括号组,您可以在调用之前将其扩展\name
\documentclass{article}
\newcommand{\tunnel}[2]{{#1}{#2}}
\newcommand{\name}[2]{\textbf{#1}~\textit{#2}}
\begin{document}
Desired output is \name{hello}{you}
Actual output is \expandafter\name\tunnel{hello}{you}
\end{document}
但这在很大程度上取决于实现细节\newcommand
,例如,如果\tunnel
定义为
\newcommand{\tunnel}[3][]{{#2}{#3}}
使用未使用的可选参数,或者如果它已由xparse
包定义\NewDocumentCommand
,那么它将无法在单个扩展步骤中完成,因此\expandafter
这里显示的不会产生所需的两个括号组\name
。
答案2
你的评论
谢谢你的回答!那个“lambda-caculusish”技巧已经在已接受帖子下面的讨论中提到过,但还是谢谢你提到它。此外,使用可选参数来获得双面的想法功能, 50% 正常, 50% “函数化”太棒了。谢谢你的提示!
促使我在我的回答中添加了一些评论:
您使用 LaTeX 进行编程。LaTeX 基于 TeX,如 Donald Ervin Knuth 的 TeXbook 中所述。基本上,LaTeX 只是一组用 TeX 编写的宏,并打包为所谓的格式,以便在通过名为 latex/latex.exe/whatsoever 的可执行文件加载 TeX 程序时自动加载这些宏。TeXbook
中介绍的低级概念也适用于 LaTeX。
因此,在以下解释中,我使用短语“TeX”的内容也适用于使用 LaTeX 进行编程。
在下面的解释中,我从未使用过这个词“功能”我不会这么做,因为——desite expl3 的误导(在我看来 ;-) )术语中经常使用“功能”一词——TeX 的编程范式不像 Pascal、C++ 或 Java 等高级编程语言那样是过程式/功能式的,而是基于宏的声明式和符号式的,其中符号由所谓的标记形成,并且在扩展阶段符号/标记会被其他符号/标记替换。
当我开始学习 TeX 和 LaTeX 时,我试图立即将我所了解的关于高级语言(如 Pascal、C++ 和 Java)编程的知识转移到 TeX/LaTeX。
这样做并没有给我带来任何好处。它让我的 TeX/LaTeX 学习曲线变得不必要地陡峭。
当时我并不清楚这种转移是否应该与将一种编程范式的概念和术语转移到另一种完全不同的编程范式齐头并进。
我认为,当仍处于学习和熟悉其中一种编程范式的概念和术语的阶段时,这种转移尝试无法很好地发挥作用。
我认为,只有当您非常熟悉这两种编程范式的概念和术语时,这种转移尝试才会有效。
这就是为什么我认为,至少在开始学习 TeX/LaTeX 时,最好严格遵守属于 TeX/LaTeX 底层编程范式的术语,并避免“借用”属于其他编程范式的术语。
在 Knuth 的消化过程类比中,TeX 具有
- 眼睛
- 消化道
- 能够产生代币并将其放入嘴里,从而开始消化过程。
TeX 的眼睛读取 .tex 输入文件。TeX 由此将输入视为一组指令,用于生成标记并将这些标记逐个放入其口中。因此,这些标记形成一个“标记流”,其元素逐个通过 TeX 的消化道。标记可以是不同风格的控制序列标记或不同风格的字符标记。
根据字符类别代码、类似参数的值\endlinechar
以及“硬编码”到 TeX 程序(或 LaTeX 程序,如果使用自动加载形成 LaTeX 格式的宏集的变体)中的内容所提供的规则,从 .tex 输入文件形成的指令生成标记。
一个(可扩展的)标记的扩展 - 即用其他标记替换该标记(以及可能构成其参数的那些标记) - 发生在标记通过 TeX 的食道传输时。
这个类比中的分配(定义宏、为\count
寄存器分配值等等)发生在 TeX 的胃里。
TeX 消化过程的最终结果将是输出文件(.pdf 文件/.dvi 文件、.log 文件、辅助文本文件(如 .aux 文件和 .toc 文件以及 .lot/.lof 文件等))和写入控制台的内容。
在对事物进行非常粗略的概述之后,让我们回到 TeX 的喉咙中发生的可扩展标记的扩展阶段:
将 TeX 宏视为在扩展过程中从标记流中删除的标记,并另外触发从标记流中删除更多标记,然后将标记插入到标记流中。“从标记流中删除更多标记”是根据以下规则进行的:⟨参数文本⟩属于宏的⟨定义⟩.“将标记插入到标记流中”是根据⟨平衡文本⟩也属于宏的⟨定义⟩. 插入到标记流中的标记构成了“替换文本”。
和\newcommand{\b}[N]{{\b1{...}}...{\bn{...}}}
作为要传递给宏的...
参数,...,你要求,...,\b
\b1
\bn
\b1
\bn
可以改变而不需要进一步的技巧,尽管它们是属于的标记⟨平衡文本⟩宏 的 定义\b
.
但如果没有进一步的扩展技巧,这是不可能的:
标记[—除了序列#1
, #2
, ...,#9
表示要根据以下规则从标记流的后续标记中收集的参数⟨参数文本⟩除了##
将简化为#
序列之外,这对于嵌套很有用⟨定义⟩在里面⟨平衡文本⟩另一个⟨定义⟩来自⟨平衡文本⟩宏的⟨定义⟩\b1
(就像,...,的情况一样)在扩展该宏(在本例中为宏)导致将替换文本插入到标记流中\bn
时不会被更改/替换。\b
利用宏和宏扩展可以做什么?
您希望应用\name
的替换文本\tunnel
吗?
重新表述:扩展\tunnel
将导致将标记插入到标记流中,这些标记将用作\name
?的参数。
正如大卫卡莱尔 (David Carlisle) 在已接受帖子的评论中提到的那样,以及您在该评论的回复中所说的“类似 lambda 演算的方法”,您可以\tunnel
用一个参数来定义,您可以在其中传递一个宏令牌,该宏令牌将处理所传递的参数\tunnel
。
我现在补充一点,该参数可以是可选的,默认情况下为空:
\documentclass{article}
\newcommand{\tunnel}[3][]{#1{#2}{#3}}
\newcommand{\name}[2]{\textbf{#1}~\textit{#2}}
\begin{document}
Desired output is \name{hello}{you}
Actual output is \tunnel[\name]{hello}{you}
\end{document}
通过一个名为的小辅助宏\PassArgumentToMacroAndThenDo
,\tunnel
您还可以将内容传递给多个宏:
\documentclass{article}
\newcommand\PassArgumentToMacroAndThenDo[3]{%
%#1 <Macro> which shall process argument #3 as its first/only argument.
%#2 Tokens to insert behind the sequence <Macro>{Argument3} .
%#3 Element of a list of Arguments that are to be processed by <Macro>s.
#1{#3}#2%
}%
\newcommand\ProcessFirstArg[1]{\par\noindent First Arg processed by \texttt{\string\ProcessFirstArg}: #1.}
\newcommand\ProcessSecondArg[1]{\par\noindent Second Arg processed by \texttt{\string\ProcessSecondArg}: #1.}
\newcommand\ProcessThirdArg[1]{\par\noindent Third Arg processed by \texttt{\string\ProcessThirdArg}: #1.}
\newcommand\ProcessFourthArg[1]{\par\noindent Fourth Arg processed by \texttt{\string\ProcessFourthArg}: #1.}
\newcommand\ProcessFifthArg[1]{\par\noindent Fifth Arg processed by \texttt{\string\ProcessFifthArg}: #1.}
\newcommand\ProcessSixthArg[1]{\par\noindent Sixth Arg processed by \texttt{\string\ProcessSixthArg}: #1.}
\newcommand\ProcessSeventhArg[1]{\par\noindent Seventh Arg processed by \texttt{\string\ProcessSeventhArg}: #1.}
\newcommand\ProcessEighthArg[1]{\par\noindent Eighth Arg processed by \texttt{\string\ProcessEighthArg}: #1.}
\newcommand{\tunnel}[9][]{#1{\texttt{\string\tunnel}-processed-#2}%
{\texttt{\string\tunnel}-processed-#3}%
{\texttt{\string\tunnel}-processed-#4}%
{\texttt{\string\tunnel}-processed-#5}%
{\texttt{\string\tunnel}-processed-#6}%
{\texttt{\string\tunnel}-processed-#7}%
{\texttt{\string\tunnel}-processed-#8}%
{\texttt{\string\tunnel}-processed-#9}}
\begin{document}
\PassArgumentToMacroAndThenDo\ProcessFirstArg{%
\PassArgumentToMacroAndThenDo\ProcessSecondArg{%
\PassArgumentToMacroAndThenDo\ProcessThirdArg{%
\PassArgumentToMacroAndThenDo\ProcessFourthArg{%
\PassArgumentToMacroAndThenDo\ProcessFifthArg{%
\PassArgumentToMacroAndThenDo\ProcessSixthArg{%
\PassArgumentToMacroAndThenDo\ProcessSeventhArg{%
\PassArgumentToMacroAndThenDo\ProcessEighthArg{%
}%
}%
}%
}%
}%
}%
}%
}%
{A}{B}{C}{D}{E}{F}{G}{H}
\bigskip
\tunnel{A}{B}{C}{D}{E}{F}{G}{H}
\bigskip
\tunnel[%
\PassArgumentToMacroAndThenDo\ProcessFirstArg{%
\PassArgumentToMacroAndThenDo\ProcessSecondArg{%
\PassArgumentToMacroAndThenDo\ProcessThirdArg{%
\PassArgumentToMacroAndThenDo\ProcessFourthArg{%
\PassArgumentToMacroAndThenDo\ProcessFifthArg{%
\PassArgumentToMacroAndThenDo\ProcessSixthArg{%
\PassArgumentToMacroAndThenDo\ProcessSeventhArg{%
\PassArgumentToMacroAndThenDo\ProcessEighthArg{%
}%
}%
}%
}%
}%
}%
}%
}%
]{A}{B}{C}{D}{E}{F}{G}{H}
\end{document}
通过另一个基于\ronannumeral0
扩展的辅助宏机制,它可以递归收集用户指定的任意数量的参数,您可以以某种方式安排事物,其中可以传递给宏的每个事物不一定只\tunnel
包含一个参数:
\documentclass{article}
\makeatletter
\newcommand\PassKArgumentsToMacroAndThenDo[3]{%
%#1 TeX <number>-quantity denoting the non-negative integer-number <K>.
%#2 <Macro> which shall process next <K> Arguments.
%#3 Tokens to insert behind the sequence <Macro>{Argument1}..{ArgumentK} .
\romannumeral0\expandafter\PassKArgumentsToMacroAndThenDoLoop\expandafter{\romannumeral0\number\number#1 000}{#2}{#3}{}%
}%
\newcommand\PassKArgumentsToMacroAndThenDoLoop[4]{%
%#1 Sequence of letters m in the amount of arguments to collect
%#2 <Macro> which shall process next <K> Arguments
%#3 Tokens to insert behind the sequence <Macro>{Argument1}..{ArgumentK}
%#4 Arguments collected so far
\ifx D#1D\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
{ #2#4#3}%
{\PassKArgumentsToMacroAndThenDoLoopFetchNextArg{#1}{#2}{#3}{#4}}%
}%
\newcommand\PassKArgumentsToMacroAndThenDoLoopFetchNextArg[5]{%
%#1 Sequence of letters m in the amount of arguments to collect
%#2 <Macro> which shall process next <K> Arguments
%#3 Tokens to insert behind the sequence <Macro>{Argument1}..{ArgumentK}
%#4 Arguments collected so far
%#5 Next argument
\expandafter\PassKArgumentsToMacroAndThenDoLoop\expandafter{\@firstoftwo{}#1}{#2}{#3}{#4{#5}}%
}%
\makeatother
\newcommand\ProcessFirstAndSecondArg[2]{%
\par\noindent First Arg processed by \texttt{\string\ProcessFirstAndSecondArg}: #1.%
\par\noindent Second Arg processed by \texttt{\string\ProcessFirstAndSecondArg}: #2.%
}%
\newcommand\ProcessThirdAndFourthAndFifthArg[3]{%
\par\noindent Third Arg processed by \texttt{\string\ProcessThirdAndFourthAndFifthArg}: #1.%
\par\noindent Fourth Arg processed by \texttt{\string\ProcessThirdAndFourthAndFifthArg}: #2.%
\par\noindent Fifth Arg processed by \texttt{\string\ProcessThirdAndFourthAndFifthArg}: #3.%
}%
\newcommand\ProcessSixthSeventhAndEighthArg[3]{%
\par\noindent Sixth Arg processed by \texttt{\string\ProcessSixthSeventhAndEighthArg}: #1.%
\par\noindent Seventh Arg processed by \texttt{\string\ProcessSixthSeventhAndEighthArg}: #2.%
\par\noindent Eighth Arg processed by \texttt{\string\ProcessSixthSeventhAndEighthArg}: #3.%
}
\newcommand{\tunnel}[9][]{#1{\texttt{\string\tunnel}-processed-#2}%
{\texttt{\string\tunnel}-processed-#3}%
{\texttt{\string\tunnel}-processed-#4}%
{\texttt{\string\tunnel}-processed-#5}%
{\texttt{\string\tunnel}-processed-#6}%
{\texttt{\string\tunnel}-processed-#7}%
{\texttt{\string\tunnel}-processed-#8}%
{\texttt{\string\tunnel}-processed-#9}}
\begin{document}
\PassKArgumentsToMacroAndThenDo{2}\ProcessFirstAndSecondArg{%
\PassKArgumentsToMacroAndThenDo{3}\ProcessThirdAndFourthAndFifthArg{%
\PassKArgumentsToMacroAndThenDo{3}\ProcessSixthSeventhAndEighthArg{%
}%
}%
}%
{A}{B}{C}{D}{E}{F}{G}{H}
\bigskip
\tunnel{A}{B}{C}{D}{E}{F}{G}{H}
\bigskip
\tunnel[%
\PassKArgumentsToMacroAndThenDo{2}\ProcessFirstAndSecondArg{%
\PassKArgumentsToMacroAndThenDo{3}\ProcessThirdAndFourthAndFifthArg{%
\PassKArgumentsToMacroAndThenDo{3}\ProcessSixthSeventhAndEighthArg{%
}%
}%
}%
]{A}{B}{C}{D}{E}{F}{G}{H}
\end{document}
答案3
您的示例中的宏\tunnel
毫无意义,因为\tunnel{xx}{yy}
与相同{xx}{yy}
。我修改了您的宏,\tunnel
以便它有意义:
\def\tunnel#1#2{{A:#1}{B:#2}}
\def\name#1#2{{\bf#1}~{\it#2}}
\expandafter\name\tunnel{hello}{you}
\bye
本例中使用\expandafter
的是对 进行第一级扩展\tunnel
,因此我们有\name{A:hello}{B:you}
。现在,\name
宏开始工作。