\let
我在修复我编写的宏时遇到了一些问题。我认为这与、\def
和有关\edef
。我经历了:
但我似乎还是无法弄清楚我误解了什么。代码有点奇怪,但这是我能想到的最简单的与我的实际代码相似的例子。
我想要的是:
- 告诉我我做错了什么以及如何改正。特别是,我对(显然是错误的)理解
\let
让我相信这一点应该已经成功了。我尝试过\def
,但\edef
没有成功。 - 如果该示例足够清楚,表明我完全错误地处理了这个问题,请建议一种更好的编写此类宏的方法。(尽管我有点担心,由于我已经将其简化了很多,建议的重写不够灵活或会错过我的实际用例。)
代码:
\documentclass{minimal}
\usepackage{etoolbox}
\makeatletter
% in actual code these have multiple required arguments
\def\myobjectXA#1{XA:#1}
\def\myobjectYA#1{YA:#1}
\def\myobjectXB#1{XB:#1}
\def\myobjectYB#1{YB:#1}
\newcommand{\makefunc}[3]{%
\ifstrequal{#1}{A}
{%
\let\myobjectX\myobjectXA
\let\myobjectY\myobjectYA
}%
{%
\let\myobjectX\myobjectXB
\let\myobjectY\myobjectYB
}
\expandafter\def\csname #2\endcsname##1{%
\ifstrequal{##1}{X}%
{\def\mytemp{\myobjectX{#3}}}
{\def\mytemp{\myobjectY{#3}}}
% In actual code \mytemp is is sent to another macro, which then
% adds additional required arguments. This is why I think using
% \let is not an option. Using \edef also didn't work for me.
\mytemp
}
}
\makeatother
\begin{document}
\setlength{\parindent}{0pt}
\makefunc{A}{hello}{1}
\makefunc{B}{goodbye}{2}
\hello{X}\\ % desired: XA:1 actual: XB:1
\hello{Y}\\ % desired: YA:1 actual: YB:1
\goodbye{X}\\ % XB:2
\goodbye{Y} % YB:2
\end{document}
以下是针对评论做出的一些补充信息:
就兼容性而言,我只关心最新的引擎。
这个例子的动机是我有两个家族 X 和 Y,它们代表你是否想要在符号顶部放置鱼叉或箭头。在家族中,有 3 个选项 A、B、C,它们指定它是指向左边、指向右边还是指向左边。所以我正在制作一个宏,让人们可以自定义基础对象和装饰器家族。我希望人们会这样写:
\makemacro{arrow}{Qaz}{Q}
\makemacro{harpoon}{Qwe}{W}
\Qwe(>) % W with right-pointing harpoon
\Qaz(<) % Q with left-pointing arrow
\Qaz(<)[0][5] % Q with left-pointing arrow and subscript 0:5
\Qaz(<)[][5] % Q with left-pointing arrow and subscript :5
\Qaz(<)[0][] % Q with left-pointing arrow and subscript 0:
\Qaz(<)[0] % Q with left-pointing arrow and subscript 0
因此,我使用字符串比较并不是绝对必要的,但我认为可读性很好。也许我可以使用 xkeyval 或类似的东西来代替。\ifstrequal
演示代码中的两个检查是在宏定义时选择系列(箭头或鱼叉)以及在“运行时”选择指向方向的快速选择。无论如何,希望这能提供足够的信息来弄清楚我如何正确使用\edef
。
答案1
当你编译以下文档主体时你应该会看到你的错误:
\makefunc{A}{hello}{1}
\hello{X}\\ % XA:1
\hello{Y}\\ % YA:1
\makefunc{B}{goodbye}{2}
\goodbye{X}\\ % XB:2
\goodbye{Y} % YB:2
输出如您所愿。原因是当您创建函数\hello
(通过\makefunc{A}
)时,内部定义请求使用\myobjectX
和\myobjectY
。这些是\let
其他一些神奇的定义。但是,随后调用\makefunc{B}
,\myobjectX
和\myobjectY
现在被设置为完全不同的东西(覆盖);某些东西\hello
对没有影响。上述在定义之前定义\goodbye
和使用的延迟\hello
产生了正确的输出。
您可以创建一个特定于宏的\myobject
,就像我下面所做的那样。我使用了一个\csname ...\endcsname
构造:
\documentclass{article}
\usepackage{etoolbox}
% in actual code these have multiple required arguments
\def\myobjectXA#1{XA:#1}
\def\myobjectYA#1{YA:#1}
\def\myobjectXB#1{XB:#1}
\def\myobjectYB#1{YB:#1}
\newcommand{\makefunc}[3]{%
\ifstrequal{#1}{A}
{%
\expandafter\let\csname myobjectX#2\endcsname\myobjectXA
\expandafter\let\csname myobjectY#2\endcsname\myobjectYA
}%
{%
\expandafter\let\csname myobjectX#2\endcsname\myobjectXB
\expandafter\let\csname myobjectY#2\endcsname\myobjectYB
}%
\expandafter\def\csname #2\endcsname##1{%
\ifstrequal{##1}{X}%
{\def\mytemp{\csname myobjectX#2\endcsname{#3}}}
{\def\mytemp{\csname myobjectY#2\endcsname{#3}}}
\mytemp%
}%
}
\begin{document}
\setlength{\parindent}{0pt}
\makefunc{A}{hello}{1}
\makefunc{B}{goodbye}{2}
\hello{X}\\ % XA:1
\hello{Y}\\ % YA:1
\goodbye{X}\\ % XB:2
\goodbye{Y} % YB:2
\end{document}
答案2
如果只需要获取适当的宏名,则根本不需要进行任何测试,因为可以使用\csname
\newcommand\makefunc[3]{%
\expandafter\def\csname #2\endcsname##1{%
\csname myobject##1#1\endcsname{#3}%
}%
}
这甚至可以用于更复杂的名称,因为您可以简单地使用\cnsame
定义来获得各种不同的结果宏。
另一方面,如果您不希望输入文本和它选择的内部宏之间有严格的等价性,那么您确实需要进行测试。对于现代引擎来说,最简单的方法可能是使用\pdfstrcmp
或等效。这是低级语法,但效果很好。我们可以不用\edef
(每次使用时进行比较)来做到这一点
\usepackage{pdftexcmds}
\makeatletter
\newcommand\makefunc[3]{%
\expandafter\def\csname #2\endcsname##1{%
\csname
myobject%
\ifnum\pdf@strcmp{\unexpanded{##1}}{X}=\z@ X\else Y\fi
\ifnum\pdf@strcmp{\unexpanded{#1}}{A}=\z@ A\else B\fi
\endcsname{#3}%
}%
}
或者使用\edef
\usepackage{pdftexcmds}
\makeatletter
\newcommand\makefunc[3]{%
\expandafter\edef\csname #2\endcsname##1{%
\noexpand\csname
myobject%
\unexpanded{\ifnum\pdf@strcmp{\unexpanded{##1}}{X}=\z@ X\else Y\fi}%
\ifnum\pdf@strcmp{\unexpanded{#1}}{A}=\z@ A\else B\fi
\noexpand\endcsname{#3}%
}%
}
\makeatother
要看出差异,请\show\hello
对两种情况进行操作。
答案3
如果我明白你需要什么,你打算编写一个\makefunc
执行以下操作的宏:
\makefunc \hello A1
\makefunc \goodbye B2
\hello X % -> XA:1
\hello Y % -> YA:1
\goodbye X % -> XB:2
\goodbye Y % -> YB:2
仅通过一行定义就可以完成此操作:
\def\makefunc#1#2#3{\def#1##1{##1#2:#3}}
请注意,没有\let
、没有\edef
、没有\pdfstrcmp
等。简单的任务可以用简单的工具完成。
\Qwe
现在来讨论有关宏的问题的第二部分\Qaz
。
\def\makemacro#1#2#3{%
\def#1(##1){{\makemacroK{#3}^{\csname\ifx##1<left\else right\fi#2\endcsname}}\makemacroA}%
}
\def\makemacroA{\def\makemacroI{}\futurelet\next\makemacroB}
\def\makemacroB{\ifx\next[\expandafter\makemacroC\fi}
\def\makemacroC[#1]{\def\makemacroI{#1}\futurelet\next\makemacroD}
\def\makemacroD{\ifx\next[\expandafter\makemacroE\else_{\makemacroI}\fi}
\def\makemacroE[#1]{_{\makemacroI:#1}}
\def\makemacroK#1{\mathop{%
\setbox0=\hbox{$#1_0$}\setbox2=\hbox{$#1\null_0$}%
#1\kern\wd0\kern-\wd2}\limits
}
\makemacro \Qaz {arrow}{Q}
\makemacro \Qwe {harpoonup}{W}
$ \Qwe(>), % W with right-pointing harpoon
\quad \Qaz(<), % Q with left-pointing arrow
\quad \Qaz(<)[0][5] % Q with left-pointing arrow and subscript 0:5
\quad \Qaz(<)[][5] % Q with left-pointing arrow and subscript :5
\quad \Qaz(>)[0][] % Q with right-pointing arrow and subscript 0:
\quad \Qaz(<)[0] % Q with left-pointing arrow and subscript 0
$
请注意,基本宏问题也在一行中解决。由于将箭头置于核心上方,计算核心与下标之间的字距(\makemacroK
)以及扫描方括号中的可选参数(\makemacroA,B,...,E
),因此做了很多工作。数学排版方案如下:
\mathord{\mathop{nucleus kerning correction}\limits^{arrow}}_{subscript}