可变个数的参数:生成对齐字符

可变个数的参数:生成对齐字符

我正在尝试定义一个\letin应该产生如下所示内容的命令。

结果

请注意,它应该支持可变数量的“let”参数和单个“in”参数。例如,以下命令

\letin{
  {x = 1}
  {y = 2}
  {z = 3}
}{x*y + z}

应扩大到

\begin{aligned}[t]
  \textbf{let }&x = 1,\\
  &y = 2,\\
  &z = 3\\
  \textbf{in }&x*y + z
\end{aligned}

基于这个答案,我得出了下面的代码。这似乎有效,除了一个导致错误的对齐字符。该字符位于命令主体内\ifx,导致错误Incomplete \ifx; ...。有办法解决这个问题吗?

\usepackage{amsmath}

\newif\ifletinsep       % --> this condition is used to prevent a comma for the last "let" argument
\newcommand*{\letin}[2]{% --> this is the command we want
\begin{aligned}[t]
\textbf{let }
\letinsepfalse
\letinscan#1\relax\\
\textbf{in }&#2
\end{aligned}
}
\newcommand{\letinscan}[1]{% --> this command processes the "let" arguments
  \ifx\relax#1\empty
  \else
    \ifletinsep
      , \\
    \else
      \letinseptrue
    \fi
    &#1           % ----> when I remove this alignment character, the command executes without error
    \expandafter\letinscan
  \fi
}

答案1

使用之前的显示来说明该\letin命令:

\documentclass{article}
\usepackage{amsmath}

\ExplSyntaxOn

\NewDocumentCommand{\letin}{mm}
 {
  \safron_letin:nn { #1 } { #2 }
 }

\seq_new:N \l__safron_letin_assign_seq

\cs_new_protected:Nn \safron_letin:nn
 {
  \seq_set_split:Nnn \l__safron_letin_assign_seq { \\ } { #1 }
  \openup-\jot
  \begin{aligned}[t]
  \textbf{let~} & \seq_use:Nn \l__safron_letin_assign_seq { \\ & } \\
  \textbf{in~} & #2
  \end{aligned}
 }

\ExplSyntaxOff

\begin{document}

\begin{align*}
  \mathit{True} &\mapsto [\mathit{true}] && \textsc{\small Rule 1} \\
  e_1 \mathbin{\mathrm{or}} e_2 &\mapsto
    \letin{
      e_1 \mapsto [a], \\
      e_2 \mapsto [b]
    }{[a \lor b]}
 && \textsc{\small Rule 2}
\end{align*}

\end{document}

在此处输入图片描述

在这里我避免放大\jot,这似乎没有必要,并且我修复了另一个我之前没有看到的问题,即错误使用\operatorname:你想要的\mathbin{\mathrm{or}}

除非找到终止参数的方法,否则无法拥有“可变数量的参数”。使用单个参数作为\\行分隔符更为直观。

这里的\\用于分隔线条,然后将其重新插入到项目之间,以便&对齐。

答案2

你几乎答对了!:-)

但是您被与 TeX 解析表格单元格的方式相关的两个细微之处“困扰”了。

您没有说是否可以解释不完全错误是如何! Incomplete \ifx; all text was ignored after line...产生的,所以让我们看看您对的定义\letinscan

\newcommand{\letinscan}[1]{% --> this command processes the "let" arguments
  \ifx\relax#1\empty
  \else
    \ifletinsep
      , \\
    \else
      \letinseptrue
    \fi
    &#1           % ----> when I remove this alignment character, the command executes without error
    \expandafter\letinscan
  \fi
}

该命令在 -environment 内执行aligned,该环境内部类似于表格,其中各个单元格之间通过 进行分隔&

因此存在两个问题:

问题 1:

表格单元格的内容形成局部范围。因此,赋值\letinseptrue被限制在由表格单元格形成的本地范围内。它需要在前面加上\global
我建议\letinsepfalse在 的定义中也在\letin赋值前面加上\global

问题2:

如果#1的第一个标记的含义等于控制字标记 的当前含义\relax\ifx\relax#1则条件为真,并且应跳过\else包括\fi所属的 的分支。\ifx但有&一个没有嵌套在括号中的\ifx在该分支中。因此,在跳过's分支时,\elseTeX“假定”当前表格单元格在该 处结束&。这会干扰\ifx's\else分支\fi位于 后面的某个位置&:由于表格单元格在遇到匹配的 之前结束\fi,因此 TeX“认为”\fi缺少 。

您可以通过添加&一组花括号来解决这个问题,如果事情没有被跳过而是被执行,这些花括号就会被丢弃,例如,作为宏的参数\iden而只吐出它的参数。

在下面的代码中对您的代码进行了四处修改,以便事情按照您的预期进行:

\newcommand\iden[1]{#1}  %%%%%%%%% modification 1: added macro \iden
\newif\ifletinsep       % --> this condition is used to prevent a comma for the last "let" argument
\newcommand*{\letin}[2]{% --> this is the command we want
  \begin{aligned}[t]%
  \textbf{let }%
  \global\letinsepfalse %%%%%%%%% modification 2: added \global
  \letinscan#1\relax\\%
  \textbf{in }&#2
  \end{aligned}%
}
\newcommand{\letinscan}[1]{% --> this command processes the "let" arguments
  \ifx\relax#1\empty
  \else
    \ifletinsep
      ,\\%
    \else
      \global\letinseptrue  %%%%%%%%% modification 3: added \global
    \fi
    \iden{&}#1% %%%%%%%%% modification 4: hide "&" in the argument-braces of \iden
    \expandafter\letinscan
  \fi
}

\documentclass{article}

\usepackage{amsmath, amssymb}

\begin{document}

\[\letin{{x = 1}{y = 2}{z = 3}}{x*y + z}\]

\noindent\hrule

\[\letin{{x = 1}{y = 2}}{x*y}\]

\noindent\hrule

\[\letin{{x = 1}}{x}\]

\noindent\hrule

\[\letin{}{\varnothing}\]

\end{document}

在此处输入图片描述



现在我们的想法是不断迭代,直到遇到一些“哨兵令牌”\relax使用您的代码,\letinstop使用我的代码)表示参数列表的结束。

说实话,所有这些\newif\ifletinsep以及\global\letinsepfalse/\global\letinseptrue或者作业欺骗对我来说似乎都很麻烦。

可能基于扩展的尾递归没有任何中间/临时分配来设置-switch \if..,但使用另一个宏参数,在第一次迭代中保持空虚,其中不插入分隔符,并,\\在后续迭代中保持-separator,也可以达到同样的效果:

\newcommand\letin[2]{%
  % #1 - List with a variable number of undelimited "let-arguments".
  %      The single "let-arguments" must not contain unbalanced
  %      \if../\else/\fi !!!
  %      The single "let-arguments" must not have a leading token
  %      whose meaning equals the meaning of \letinstop !!!
  % #2 - "in-argument"=stuff to append to bold phrase "in ".
  \begin{aligned}[t]\textbf{let }%
  \letinloop{}#1\letinstop
  \\\textbf{in }&#2\end{aligned}%
}%
\newcommand\letinstop{\letinstop}%
\newcommand\secondfirst[2]{#2#1}%
\newcommand\letinloop[2]{%
  % #1 - Separator/Tokens to prepend to & and this iteration's
  %      "let-argument".
  %      In the first iteration this argument is empty.
  %      In subsequent iterations this argument holds the tokens: ,\\
  % #2 - Either this iteration's "let-argument" or \letinstop-quark.
  \ifx\letinstop#2\empty\else\secondfirst{#1&#2\letinloop{,\\}}\fi
}%

\documentclass{article}

\usepackage{amsmath, amssymb}

\begin{document}

\[\letin{{x = 1}{y = 2}{z = 3}}{x*y + z}\]

\noindent\hrule

\[\letin{{x = 1}{y = 2}}{x*y}\]

\noindent\hrule

\[\letin{{x = 1}}{x}\]

\noindent\hrule

\[\letin{}{\varnothing}\]

\end{document}

在此处输入图片描述


而不是使用\ifx来检查是否具有sentinel-token 的含义表示已到达 let 参数列表的末尾,您可以使用基于分隔参数的机制来检查在场哨兵标记。
这样一来,以定义更多宏为代价,你可以将限制从
“单个‘let-arguments’不允许包含不平衡的 \if../ \else/\fi和单个‘let-arguments’不允许具有其含义等于\letinstop”的前导标记减少

“单个‘let-arguments’不允许由单个标记组成\letinstop”:

作为通过这种方法存在哨兵令牌检查而不是检查含义通过\ifx,则不需要定义 sentinel-token。(使用此方法,除了使用单个标记标记外,您还可以使用由一组适合作为分隔宏参数的分隔符的标记组成的标记短语。)

\makeatletter
% \CheckWhetherletinstop{<tokens to check>}%
%                       {<Tokens in case <tokens to check> consists
%                          only of the single token \letinstop>}%
%                       {<Tokens in case <tokens to check> does not
%                         consist only of the single token \letinstop>}%
% Test if the argument consists of the single token \letinstop.
% Internally an argument delimited by !\letinstop! is used.
% So first crank out the case of the argument containing ! as
% a case of the argument not being the single token \letinstop.
% Then you are safe that the argument is not something like
%   !\letinstop!whatsoever
% which would also match the delimiter and thus fool the check.
\newcommand\CheckWhetherletinstop[1]{%
  % #1 - <tokens to check>.
  % \ifcat$\detokenize{<arg to check>}$<true>\else<false>\fi 
  % checks whether the set of tokens formed by <arg to check>
  % is empty. Thus the following checks if #1 contains an
  % exclamation-mark which is not nested in curly braces.
  \ifcat$\detokenize\expandafter{\gobbletoexclam#1!}$%
  \expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
  {\letinstopFork!#1!{\@firstoftwo}!\letinstop!{\@secondoftwo}!!!!}%
  {\@secondoftwo}%
}%
\@ifdefinable\gobbletoexclam{\long\def\gobbletoexclam#1!{}}%
\@ifdefinable\letinstopFork{%
  \long\def\letinstopFork#1!\letinstop!#2#3!!!!{#2}%
}%
\makeatother

\newcommand\letin[2]{%
  % #1 - List with a variable number of undelimited "let-arguments".
  %      The single "let-arguments" must not consist of the single
  %      token \letinstop !!!
  % #2 - "in-argument"=stuff to append to bold phrase "in ".
  \begin{aligned}[t]\textbf{let }%
  \letinloop{}#1\letinstop
  \\\textbf{in }&#2\end{aligned}%
}%
\newcommand\letinloop[2]{%
  % #1 - Separator/Tokens to prepend to & and this iteration's
  %      "let-argument".
  %      In the first iteration this argument is empty.
  %      In subsequent iterations this argument holds the tokens: ,\\
  % #2 - Either this iteration's "let-argument" or \letinstop-token.
  \CheckWhetherletinstop{#2}{}{#1&#2\letinloop{,\\}}%
}%

\documentclass{article}

\usepackage{amsmath, amssymb}

\begin{document}

\[\letin{{x = 1}{y = 2}{z = 3}}{x*y + z}\]

\noindent\hrule

\[\letin{{x = 1}{y = 2}}{x*y}\]

\noindent\hrule

\[\letin{{x = 1}}{x}\]

\noindent\hrule

\[\letin{}{\varnothing}\]

\end{document}

在此处输入图片描述


评论:

在后续的编码示例中,你会发现\romannumeral
乍一看这可能有点令人困惑。

在后续的编码示例中,\romannumeral并不用于获取任何数字的小写罗马表示,而是(ab?)用于触发大量扩展工作和翻转宏参数工作,以便在需要控制扩展的情况下不需要那么多/长的\expandafter链。

\romannumeral通常用于“吞食”形成 TeX-⟨数字⟩-数量,并作为回报,他们提供构成该 TeX 值的表示的字符标记-⟨数字⟩- 数量以小写罗马数字表示。
\romannumeral在任何情况下都会触发“吞噬”那些形成 TeX-⟨数字⟩-数量。但如果 TeX-⟨数字⟩-quantity 确实有一个非正值,默默地,即,没有错误消息或类似信息,根本没有传递任何令牌作为回报。
除此之外,在搜索属于 TeX-⟨数字⟩-数量,可扩展令牌的扩展不受抑制。

因此,你可以(滥用)使用\romannumeral来搜索属于 TeX-⟨数字⟩-quantity 让 (La)TeX 做大量的扩展工作和翻转宏参数工作,只要确保最终 TeX-⟨数字⟩- 发现数量的值不是正值。

在随后的编码示例中,⟨数字⟩- 其值不是正数的数量”时使用\chardef-token \stopromannumeral/ 。\UD@stopromannumeral


您指定\letin{{x = 1}{y = 2}{z = 3}}{x*y + z}应扩展为

\begin{aligned}[t]
  \textbf{let }&x = 1,\\
  &y = 2,\\
  &z = 3\\
  \textbf{in }&x*y + z
\end{aligned}

,严格来说,根据上述代码示例,该组标记并不是作为一个整体传递的。相反,先传递并处理该组标记中位于前面的标记,然后再传递和处理该组标记中位于后面的标记。

如果您需要整个可用的标记集,例如,从中定义一个宏,或者将其作为另一个宏的参数传递,则可以修改上述方法来收集宏参数中的标记。

这样,在触发两个扩展步骤后,形成结果的整个标记集就可以作为一个整体使用\letin——第一个扩展步骤提供 的\letin顶层扩展,其第一个标记是\romannumeral。第二个扩展步骤触发\romannumeral,这反过来又触发后续扩展步骤,同时搜索属于 TeX-⟨数字⟩\stopromannumeral-quantity 直到 TeX 找到表示非正 TeX- 的标记⟨数字⟩-quantity 导致 TeX 吞噬它并结束\romannumeral-routine 而不提供令牌作为回报⟨数字⟩数量代币:

\newcommand\letin[2]{%
  % #1 - List with a variable number of undelimited "let-arguments".
  %      The single "let-arguments" must not contain unbalanced
  %      \if../\else/\fi !!!
  %      The single "let-arguments" must not have a leading token
  %      whose meaning equals the meaning of \letinstop !!!
  % #2 - "in-argument"=stuff to append to bold phrase "in ".
  \romannumeral\letinloop{\begin{aligned}[t]\textbf{let }}{}{\\\textbf{in }&#2\end{aligned}}#1\letinstop
}%
\csname @ifdefinable\endcsname\stopromannumeral{\chardef\stopromannumeral=`\^^00}%
\newcommand\letinstop{\letinstop}%
\newcommand\fot[2]{#1}%
\newcommand\sot[2]{#2}%
\newcommand\letinloop[4]{%
  % #1 - Tokens of result gathered so far.
  %      Initially this argument holds the tokens:
  %      \begin{aligned}[t]\textbf{let }
  % #2 - Separator/Tokens to prepend to & and this iteration's
  %      "let-argument".
  %      In the first iteration this argument is empty.
  %      In subsequent iterations this argument holds the tokens: ,\\
  % #3 - Tokens to append to the <tokens of the result gathered so
  %      far> when the loop is done; this argument holds:
  %      \\\textbf{in }&<in-argument>\end{aligned}
  % #4 - Either this iteration's "let-argument" or \letinstop-quark.
  \ifx\letinstop#4\empty\expandafter\fot\else\expandafter\sot\fi
  {\stopromannumeral#1#3}%
  {\letinloop{#1#2&#4}{,\\}{#3}}%
}%

\documentclass{article}

\usepackage{amsmath, amssymb}

\begin{document}

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\temp
\expandafter\expandafter\expandafter{\letin{{x = 1}{y = 2}{z = 3}}{x*y + z}}

\noindent\texttt{\string\temp:\\\meaning\temp}

\[\temp\]

\noindent\hrule

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\temp
\expandafter\expandafter\expandafter{\letin{{x = 1}{y = 2}}{x*y}}

\noindent\texttt{\string\temp:\\\meaning\temp}

\[\temp\]

\noindent\hrule

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\temp
\expandafter\expandafter\expandafter{\letin{{x = 1}}{x}}

\noindent\texttt{\string\temp:\\\meaning\temp}

\[\temp\]

\noindent\hrule

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\temp
\expandafter\expandafter\expandafter{\letin{}{\varnothing}}

\noindent\texttt{\string\temp:\\\meaning\temp}

\[\temp\]

\end{document}

在此处输入图片描述

如果您希望确保在表格环境或对齐或类似环境中执行时一切顺利,其中无括号&可能错误地与周围的表格单元格相关联,您可以通过在宏参数的花括号之间嵌套循环来实现 - \fot(=F第一oF在此示例中:

\newcommand\letin[2]{%
  % #1 - List with a variable number of undelimited "let-arguments".
  %      The single "let-arguments" must not contain unbalanced
  %      \if../\else/\fi !!!
  %      The single "let-arguments" must not have a leading token
  %      whose meaning equals the meaning of \letinstop !!!
  % #2 - "in-argument"=stuff to append to bold phrase "in ".
  \romannumeral\expandafter\fot\expandafter{%
    \expandafter\stopromannumeral
    \romannumeral\letinloop{\begin{aligned}[t]\textbf{let }}{}#1\letinstop
    \\\textbf{in }&#2\end{aligned}%
  }{}%
}%
\csname @ifdefinable\endcsname\stopromannumeral{\chardef\stopromannumeral=`\^^00}%
\newcommand\letinstop{\letinstop}%
\newcommand\fot[2]{#1}%
\newcommand\sot[2]{#2}%
\newcommand\letinloop[3]{%
  % #1 - Tokens of result gathered so far.
  %      Initially this argument holds the tokens:
  %      \begin{aligned}[t]\textbf{let }
  % #2 - Separator/Tokens to prepend to & and this iteration's
  %      "let-argument".
  %      In the first iteration this argument is empty.
  %      In subsequent iterations this argument holds the tokens: ,\\
  % #3 - Either this iteration's "let-argument" or \letinstop-quark.
  \ifx\letinstop#3\empty\expandafter\fot\else\expandafter\sot\fi
  {\stopromannumeral#1}{\letinloop{#1#2&#3}{,\\}}%
}%

\documentclass{article}

\usepackage{amsmath, amssymb}

\begin{document}

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\temp
\expandafter\expandafter\expandafter{\letin{{x = 1}{y = 2}{z = 3}}{x*y + z}}

\noindent\texttt{\string\temp:\\\meaning\temp}

\[\temp\]

\noindent\hrule

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\temp
\expandafter\expandafter\expandafter{\letin{{x = 1}{y = 2}}{x*y}}

\noindent\texttt{\string\temp:\\\meaning\temp}

\[\temp\]

\noindent\hrule

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\temp
\expandafter\expandafter\expandafter{\letin{{x = 1}}{x}}

\noindent\texttt{\string\temp:\\\meaning\temp}

\[\temp\]

\noindent\hrule

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\temp
\expandafter\expandafter\expandafter{\letin{}{\varnothing}}

\noindent\texttt{\string\temp:\\\meaning\temp}

\[\temp\]

\end{document}

在此处输入图片描述



现在的想法是不断迭代直到遇到一些“sentinel-token”(\relax用你的代码,\letinstop用我的代码)来表示参数列表的结尾。

另一种方法是将参数列表作为参数本身,然后迭代并提取/删除该参数“内部”的第一个参数,直到该参数为空(=为空或仅包含空格标记)。

优点:

  • 当迭代直到到达标记标记时,您不能在参数列表中使用该标记标记。当迭代直到出现“空白”时,则没有禁用标记。
  • 迭代直到出现空白可以可扩展地实现,而无需使用任何\if..\else..\fi-expression。因此,用户的参数包含不平衡\if\else\fi欺骗循环不会有任何危险。因此,参数列表的元素可以包含不平衡\if\else\fi。这不会干扰算法。(但如果你不知道自己在做什么,结果可能不会像你预期的那样。)

缺点:

  • 您需要一个(子)例程来检查宏参数的“空白”,并且您需要一个(子)例程来提取未限定宏参数的第一个未限定宏参数。因此,(子)例程的代码很多。出于私人目的,我可能不会这样做。我可能只会对代码必须是用户验证的/必须处理各种奇怪的用户输入的包这样做。
\makeatletter
%%=============================================================================
%% PARAPHERNALIA:
%% \UD@firstoftwo, \UD@secondoftwo, \UD@PassFirstToSecond, \UD@Exchange,
%% \UD@stopromannumeral, \UD@CheckWhetherNull, \UD@CheckWhetherBlank,
%% \UD@ExtractFirstArg
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\UD@Exchange[2]{#2#1}%
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is empty>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral\expandafter\UD@secondoftwo\string{\expandafter
  \UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@secondoftwo}{%
  \expandafter\UD@stopromannumeral\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument is blank (empty or only spaces):
%%-----------------------------------------------------------------------------
%% -- Take advantage of the fact that TeX discards space tokens when
%%    "fetching" _un_delimited arguments: --
%% \UD@CheckWhetherBlank{<Argument which is to be checked>}%
%%                      {<Tokens to be delivered in case that
%%                        argument which is to be checked is blank>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked is not blank>}%
\newcommand\UD@CheckWhetherBlank[1]{%
  \romannumeral\expandafter\expandafter\expandafter\UD@secondoftwo
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo#1{}{}}%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%.............................................................................
%%   \UD@ExtractFirstArg{ABCDE} yields  A
%%
%%   \UD@ExtractFirstArg{{AB}CDE} yields  AB
%%
%% Due to \romannumeral-expansion the result is delivered after two 
%% expansion-steps/after "hitting" \UD@ExtractFirstArg with \expandafter
%% twice.
%%
%% \UD@ExtractFirstArg's argument must not be blank.
%% This case can be cranked out via \UD@CheckWhetherBlank before calling
%% \UD@ExtractFirstArg.
%%
%% Use frozen-\relax as delimiter for speeding things up.
%% Frozen-\relax is chosen because David Carlisle pointed out in
%% <https://tex.stackexchange.com/a/578877>
%% that frozen-\relax cannot be (re)defined in terms of \outer and cannot be
%% affected by \uppercase/\lowercase.
%%
%% \UD@ExtractFirstArg's argument may contain frozen-\relax:
%% The only effect is that internally more iterations are needed for
%% obtaining the result.
%%.............................................................................
\@ifdefinable\UD@RemoveTillFrozenrelax{%
  \expandafter\expandafter\expandafter\UD@Exchange
  \expandafter\expandafter\expandafter{%
  \expandafter\expandafter\ifnum0=0\fi}%
  {\long\def\UD@RemoveTillFrozenrelax#1#2}{{#1}}%
}%
\expandafter\UD@PassFirstToSecond\expandafter{%
  \romannumeral\expandafter
  \UD@PassFirstToSecond\expandafter{\romannumeral
    \expandafter\expandafter\expandafter\UD@Exchange
    \expandafter\expandafter\expandafter{%
    \expandafter\expandafter\ifnum0=0\fi}{\UD@stopromannumeral#1}%
  }{%
    \UD@stopromannumeral\romannumeral\UD@ExtractFirstArgLoop
  }%
}{%
  \newcommand\UD@ExtractFirstArg[1]%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
  {\expandafter\UD@stopromannumeral\UD@secondoftwo{}#1}%
  {\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillFrozenrelax#1}}%
}%
%%=============================================================================
%% \letin based on paraphernalia
%%=============================================================================
\newcommand\letin[2]{%
  % #1 - List with a variable number of undelimited "let-arguments".
  % #2 - "in-argument"=stuff to append to bold phrase "in ".
  \romannumeral\letinloop{\begin{aligned}[t]\textbf{let }}{#1}{\\\textbf{in }&#2\end{aligned}}{}%
}%
\newcommand\letinloop[4]{%
  % #1 - Tokens of result gathered so far.
  %      Initially this argument holds the tokens:
  %      \begin{aligned}[t]\textbf{let }
  % #2 - List with a variable number of undelimited "let-arguments".
  % #3 - Tokens to append to the <tokens of the result gathered so
  %      far> when the loop is done; this argument holds:
  %      \\\textbf{in }&<in-argument>\end{aligned}
  % #4 - Separator/Tokens to prepend to & and this iteration's
  %      "let-argument".
  %      In the first iteration this argument is empty.
  %      In subsequent iterations this argument holds the tokens: ,\\
  \UD@CheckWhetherBlank{#2}{\UD@stopromannumeral#1#3}{%
    \expandafter\UD@PassFirstToSecond\expandafter{\UD@firstoftwo{}#2}{%
      \expandafter\letinloop\expandafter{%
        \romannumeral
        \expandafter\expandafter\expandafter\UD@Exchange
        \expandafter\expandafter\expandafter{%
          \UD@ExtractFirstArg{#2}%
        }{\UD@stopromannumeral#1#4&}%
      }%
    }%
    {#3}{,\\}%
  }%
}%
\makeatother

\documentclass{article}

\usepackage{amsmath, amssymb}

\begin{document}

This is an obscure thing where the fact that when starting a table cell TeX keeps
expanding until a non-expandable token is found is used for removing the first
list-item via \verb|\iffalse|---the items of the argument-list contain unbalanced
\verb|\if..| and \verb|\fi|:

\bigskip

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\temp
\expandafter\expandafter\expandafter{\letin{ {\iffalse x = 1} {\fi y = 2} {z = 3} }{x*y + z}}

\noindent\texttt{\string\temp:\\\meaning\temp}

\[\temp\]

\noindent\hrule

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\temp
\expandafter\expandafter\expandafter{\letin{{x = 1}{y = 2}{z = 3}}{x*y + z}}

\noindent\texttt{\string\temp:\\\meaning\temp}

\[\temp\]

\noindent\hrule

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\temp
\expandafter\expandafter\expandafter{\letin{{x = 1}{y = 2}}{x*y}}

\noindent\texttt{\string\temp:\\\meaning\temp}

\[\temp\]

\noindent\hrule

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\temp
\expandafter\expandafter\expandafter{\letin{{x = 1}}{x}}

\noindent\texttt{\string\temp:\\\meaning\temp}

\[\temp\]

\noindent\hrule

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\temp
\expandafter\expandafter\expandafter{\letin{}{\varnothing}}

\noindent\texttt{\string\temp:\\\meaning\temp}

\[\temp\]

\end{document}

在此处输入图片描述

相关内容