理解可扩展性的指南:何时编写受保护的函数以及何时不编写受保护的函数

理解可扩展性的指南:何时编写受保护的函数以及何时不编写受保护的函数

我很难理解(并且欣赏)的概念可扩展性. 我对理解很模糊 什么时候如何可扩展性影响了我为文档编写代码。

我读了为什么并非所有事物都可扩展?。答案很有趣也很有用,但它没有触及我好奇的核心。我还仔细阅读了其他一些涉及可扩展性的问题的答案:特别有趣的是这个帖子

回答我最近的一个问题,据解释,文档命令是受保护因此不是可扩展理解了这一点,我就能写出我想写的东西,并得到我期望的效果。

在对我的另一个问题的评论中,解释了\cs_new_protected:Npn当函数执行不可扩展的工作(例如设置标记列表或序列)时应该如何使用。

多年来,我一直在写这样的代码

\newcommand{\currentanswer}{}
\newcommand{\setcurrentanswer}[1]{\renewcommand{\currentanswer}{#1}}

知道在调用 之后\setcurrentanswer,对 的任何调用\currentanswer都将产生所需的输出。我在这里依赖(不)可扩展性吗?我不太确定;我只知道它能做我想要的事情。然后有时我知道我可以加入 来\protect获得我想要的结果:但是,我真的不明白为什么我只知道它能完成工作。

最近,我一直在尝试学习一些 LaTeX3:我玩得越多,就越喜欢它。我一直认为 LaTeX 非常强大,但在定义宏和函数的方式上,它突然变得更加强大和透明。但现在,我似乎也遇到了可扩展性问题,而之前我可以轻轻松松地做自己的事情,而对一些细节一无所知。

虽然我在这里问了多个问题,但我怀疑它们确实有相同的答案:因此我不会将它们分成多个帖子。

  1. 有人可以花时间解释一下可扩展性的一些细微差别吗?或者,如果不行的话,可以给我提供一个好的参考吗?

  2. 我怎么知道我什么时候在和受保护函数/宏?

  3. 受保护不可扩张一样的东西?

  4. 有人能解释一下受保护LaTeX3 中的函数?

  5. 最后,除了上述问题的答案之外,为什么最好保护执行不可扩展任务的函数:例如设置标记和序列?(我是很感兴趣理解最后一个问题。

答案1

可扩展命令是指可以在 TeX\edef\write(以及一些其他地方)中“完全”转换为其输出的命令。例如

\def\testa{\testb}
\def\testb{\testc}
\def\testc{d}
\edef\teste{\testa}
\show\teste

会给

> \teste=macro:
->d.

IE所有步骤都已展开,我们只剩下字符。

对于文本来说,这很简单,但是当你涉及到 TeX 基元时,事情就变得更加复杂了,因为有些基元是可扩展的,有些则不是。广义上讲,任何执行赋值的东西都是不可扩展的。所以如果我们有

\def\testa{\testb}
\def\testb{\testc}
\def\testc{\def\ARG{d}}
\def\ARG{}
\edef\teste{\testa}
\show\teste

我们得到

> \teste=macro:
->\def {d}.

请注意,保持\def不变但\ARG消失了:它扩展为定义的状态(空)。

e-TeX 允许我们定义受保护宏。这些不会在 内部扩展\edef,因此

\def\testa{\testb}
\def\testb{\testc}
\def\testc{\def\NOTARG{d}}
\protected\def\NOTARG{}
\edef\teste{\testa}
\show\teste

现在收益

> \teste=macro:
->\def \NOTARG {d}.

这里有一个微妙但重要的观点:\def一个不可扩展的原始,而\NOTARG现在则是受保护宏。你可以\NOTARG使用以下命令判断它是否受到保护\show

> \NOTARG=\protected macro:
->.

告诉\protected我们需要知道的内容。但是,你必须知道不可\def扩展。


在 LaTeX3 文档中,我们采用了不同的方法,而不是期望人们学习规则:我们记录哪些函数是可扩展的(它们用星号标记)。其他所有内容都受到保护的原因是“部分”扩展是一个真正的问题。如果你这样做

\def\testa{\let\testb\testc}
\edef\testb{\testa}

你得到

! Undefined control sequence.
\testa ->\let \testb 
                     \testc 

因为\let不受影响,\edef\testb未定义。当您查看“真实”文档时,情况会变得更糟,因为问题可能隐藏在许多层之下。

人们在实际的 LaTeX2e 文档中看到的许多问题(例如他们忘记\protect和遇到麻烦的地方)如果大多数命令受到保护,就会被绕过。一般来说,你会发现不可扩展的 (La)TeX 代码比可扩展的代码多得多,所以 LaTeX3 的立场是这是例外,当然对于文档命令。(排版是不可扩展的,这就是文档中发生的情况。)

这引出了我所说的“羊和山羊”方法:所有 LaTeX3 代码要么是受保护的,要么是完全可扩展的 [“安全”(将给出预期结果)在\edef/x类型扩展中],即使我们谈论的是辅助函数。结果是,我们始终可以确定函数是否可以在扩展上下文中使用:如果可以,则用星号标记,否则它将受到保护并且不会部分扩展。因此,编写 LaTeX3 代码的“正确”方法是,如果您使用任何事物不可扩展(IE未在文档中加星号)在你的代码中,那么你使用\cs_new_protected:Npn或类似用途,以及不是 \cs_new:NpnETC。

答案2

\newcommand{\currentanswer}{}
\newcommand{\setcurrentanswer}[1]{\renewcommand{\currentanswer}{#1}}

您的设置命令不可扩展,它\renewcommand本身会扩展到其他东西,但最终它会任务(使用\def)它设置\currentanswer为传入的标记#1。但是,宏\currentanswer 被定义为扩展为传入的值,因此只要传入的值是安全的,就可以安全地在扩展上下文中使用。

注意如讨论的 完全可扩展宏的优点和缺点 将命令分类为可扩展或不可扩展其实没有任何意义,任何宏的定义都会扩展为其定义,区别在于上下文,无论你是仅有的进行扩展,或者您是否处于 TeX 的正常操作模式,其中扩展与分配和其他不可扩展的原始操作交错。

  1. 可以尝试,但另请参阅上面的链接

  2. 你需要查看它的文档或定义。有两种不同的保护机制:LaTeX\protect系统和 e-tex\protected\def系统。

    *\show\pounds
    > \pounds=macro:
    ->\protect \pounds  .
    <*> \show\pounds
    
    ? 
    
    *\protected\edef\foo{abc}
    
    *\show\foo
    > \foo=\protected macro:
    ->abc.
    <*> \show\foo
    
    ? 
    

    上述交互式会话显示\pounds的定义为\protect\pounds(那里的第二个标记在其名称末尾有一个空格)因此依赖于 的 LaTeX 定义\protect

    \foo使用 e-TeX 保护机制(LaTeX3 命令最常使用的机制)进行定义,并在输出中显示\show。标记\foo受 tex 引擎本身保护(因此不会在仅扩展的上下文中扩展),并且不需要内部宏来保存实际定义,就像 的情况一样\pounds

  3. 不可以。不可扩展的原语(比如\def在胃中工作)和受保护的命令都是可扩展的宏,但是您需要它们在某些情况下不扩展(例如,它们将辅助文件作为命令名而不是其当前定义写入)受保护的命令通常是可扩展的,但在仅扩展的上下文中充当不可扩展的命令。

  4. 由于它们带来的意外较少,因此更受青睐。(通常情况下)。

  5. 如果在写入或例如\edef需要\caption将文本写入文件的地方使用未受保护的命令,则该命令将会失败.toc

答案3

整个可扩展性主题需要或多或少陡峭的学习曲线。

这是一个很长的回答。归根结底,我试图总结一下我应该从哪里开始 LaTeX 编程?

并且考虑到一些善意,它需要一些工具知识来显示代码的实际结果。

如果你更多地了解所涉及的内部原理,例如如何定义宏,就可以理解可扩展性的概念没有或者扩大论点。

直截了当地说: 的结果是什么\renewcommand{\currentanswer}{\content}? 会\currentanswer扩展为\content,进而扩展为它“包含”的内容?还是 会在 之后\currentanswer包含 的“值”的副本?当然,只有在 之后立即发生变化时,差异才是可衡量的。\content\renewcommand\content\renewcommand

虽然这看起来是一个技术区别,但它与您的问题直接相关。

如果在命令后立即\section{\currentanswer}修改值,会发生什么情况?\currentanswer\section

另一个问题是:如果你写会发生什么\section{\tikz \draw ... ;}?显然,这是一个不同的用例:我们放置了一些可执行文件部分参数内的代码。

\def简短的回答是:您可能需要了解和\edef(“扩展” )之间的区别\def。并且您应该知道\section{}用于\edef收集其参数;这同样适用于 LaTeX 中的其他“可移动”参数(提供给分段命令、索引、交叉引用等的任何内容)。归结\renewcommand\def;它不会扩展参数。在 的主体内部\edef,任何条件都将被扩展。任何“可执行”的内容都将不是可以扩展,需要保护。这是一条经验法则;您仍然需要了解“可执行”的含义。但通常,直觉就足够了(例如“包含值的宏不可扩展”,而“\tikz ... ;当然可以扩展”)。

有趣的辅助实用程序是\show<macroname>检查\message{The meaning of content is \meaning\content^^J}您在自己的代码中所写的内容。

此外,在 LaTeX 中,如果您将任何“可执行”参数作为某些分段命令的参数提供,则该参数应受到保护。保护通常意味着\protect\macro

我建议阅读我应该从哪里开始 LaTeX 编程?如果您想了解有关这些概念的更多信息。它可能会帮助您理解 LaTeX3 的相关高级概念。

答案4

关于如何编写与可扩展性有关的 expl3 代码的一些补充说明。


回想起那个

  • 如果一个函数能够发挥作用,那么它就被称为“可扩展”如你所愿在仅扩展上下文中。

  • 在那些无法扩展的,那些

    • 在仅扩展上下文中不会出错,并且
    • 在正常情况下最终执行时做正确的事情

    叫做强壮的其余的被称为“脆弱”。

  • 你应该保护不可扩展的函数,否则它可能会很脆弱,而脆弱的命令错误的事情当您意外地/需要将其放在仅扩展的上下文中(例如移动参数)时。


假设你想做类似的事情(类似 C 语法的伪代码)

result = f(g(h(argument)))

其中argumentresult是变量,fgh是函数,应该返回一些结果。

如果所有这些都是可扩展的,那么你可以简单地做

\tl_set:Ne \result { \f:e { \g:e { \h:e { \argument } } } }

但是,如果有些不可扩展怎么办?显然你不能直接在参数中传递它,因为决不供外部宏访问结果。

这里常见的 TeX 习惯用法是该函数将结果存储到某个地方

在 expl3 代码中,目标通常作为 N 类型参数提供。(与 pgf 相比,数学结果存储在中\pgfmathresult

假设f不可扩展,它通常具有以下形式

\f:Nn <target> <argument>

或者

\f:N <data>   % for example regex_replace_all

在这种情况下,您需要“展开”调用链:

\h:Ne \result {\argument}
\g:Ne \result {\result}
\f:Ne \result {\result}

相关内容