编写宏的宏和意外的副作用

编写宏的宏和意外的副作用

\let我在修复我编写的宏时遇到了一些问题。我认为这与、\def和有关\edef。我经历了:

\let 和 \edef 之间有什么区别?

但我似乎还是无法弄清楚我误解了什么。代码有点奇怪,但这是我能想到的最简单的与我的实际代码相似的例子。

我想要的是:

  1. 告诉我我做错了什么以及如何改正。特别是,我对(显然是错误的)理解\let让我相信这一点应该已经成功了。我尝试过\def,但\edef没有成功。
  2. 如果该示例足够清楚,表明我完全错误地处理了这个问题,请建议一种更好的编写此类宏的方法。(尽管我有点担心,由于我已经将其简化了很多,建议的重写不够灵活或会错过我的实际用例。)

代码:

\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}

相关内容