20 \renewcommand 对 \AddToHook 的锚点重定义后、\AddToHook 的作用失效?

发布于 2025-04-16 20:24:54

配置

Win11; TeXLive2024; VSCode; XeLaTeX.

问题描述

对命令\foo加了一个钩子:\AddToHook{cmd/foo/before}{...}以追加特定文本,但在文中用\renewcommand\foo重定义后、在使用\foo命令,所生成的内容并未被追加该特定文本。

啥原因呢;怎样可以使钩子继续生效呢。

已尝试的做法

  • 在使用\renewcommand之后再用一次相同的钩子:钩子的作用依旧无法生效

MWE

\documentclass{article}

\newcommand{\foo}{target}
\AddToHook{cmd/foo/before}{extra }

\begin{document}
\foo

\renewcommand{\foo}{new target}
\foo
\end{document}

效果:
image.png

查看更多

关注者
0
被浏览
202
雾月
雾月 2天前
这家伙很懒,什么也没写!
2 个回答
Sagittarius Rover
我要成为Typst糕手/(ㄒoㄒ)/~~

对于原OP的MWE:

\documentclass{article}

\newcommand{\foo}{target}%<-mark1

\AddToHook{cmd/foo/before}{extra }%<-mark2
\begin{document}%<-mark3
\foo%<-mark4

\renewcommand{\foo}{new target}%<-mark5
\foo%<-mark6
\end{document}

我来尝试理解一下每一步发生的过程(我希望我的理解没有大问题):

  • mark1:一切照常,定义了新命令\foo
  • mark2:在导言区中使用\AddToHook 由于Hook不会物理性地在导言区存在,而是会有一个延迟修补的机制,在此时增加extra 的内容尚且不会直接被写入\foo的定义中
  • mark3:此时begindocument的钩子自动执行,在此时被延迟写入\fooextra 终于被自动修补\foo的定义中
  • mark4:由于自动修补已经完成,此时输出\foo结果为extra target
因为对于通用钩子在使用\UseHookWithArguments进行自动修补的同时会新建这个钩子,而钩子只能被\NewHook一次,因此可以预见这种自动修补只会进行一次,这里在mark3的时刻,已经由通用钩子cmd/foo/begin的自动修补机制实现了对cmd/foo/begin的创建以及补充代码,故后续不会再进行自动修补
  • mark5:重定义\foo的同时,覆盖了原先的自动修补内容extra .且由于cmd/foo/begin钩子已经存在,不会被再次创建,此时可以使用雾月老师指出的『最佳实践』(也即文档section 2.1.1的第三段):
This has the consequence that a command defined or redefined after \begin{document} only uses generic cmd hook code if \AddToHook is called for the first time after the definition is made, or if the command explicitly uses the generic hook in its definition by declaring it with \NewHookPair adding \UseHook as part of the code.
[kimi翻译版] 这意味着,如果一个命令是在\begin{document}之后定义或重新定义的,只有在定义后首次调用\AddToHook,或者命令在定义时明确使用了通用钩子(通过\NewHookPair声明并包含\UseHook作为代码的一部分),该命令才会使用通用命令钩子代码。

(O.S.我好像没在哪看到\NewHookPair的用法,但这里最后一句的说法我猜就是『最佳实践』的方案)

  • mark6:由于没能再次自动“修补”,此时第二次输出的\foo就仅有最近的一次重定义(\renewcommand)的内容。

对于修改版的MWE:

\renewcommand{\foo}{%
\UseHookWithArguments{cmd/foo/before}{0}%
new target%
\UseHookWithArguments{cmd/foo/after}{0}%
}

要注意的是,如前面介绍,在mark3时已经通过唯一的一次自动修补机制"\NewHook"了一个名为cmd/foo/before的Hook,且其内容为在\foo之前添加extra
于是新的MWE中的\renewcommand里,使用\UseHook{cmd/foo/before}或者\UseHookWithArguments{cmd/foo/before}{0}就可以让cmd/foo/before里保存的内容(因为已经"\NewHook"过这个名为cmd/foo/before的Hook了)“再次”直接发挥作用被\Use,“手动”修补得到想要的结果extra new target.


非常感谢雾月老师,同时也有必要再补充一点以方便我的理解:

对于OP已提及的『尝试过的做法』:在\renewcommand之后再次\AddToHook:

\documentclass{article}
\newcommand{\foo}{target}
\AddToHook{cmd/foo/before}{extra }%
\begin{document}%
\LogHook{cmd/foo/before}%First
\foo%

\renewcommand{\foo}{new target}%
\AddToHook{cmd/foo/before}{extra }%
\LogHook{cmd/foo/before}%Second

%因为hookname已经被占用,不会自动修补
\foo% 因此在『已进行的尝试』中同样无效

\UseHook{cmd/foo/before}%
\foo%

% 其实就等价于『最佳实践』的做法,当需要对重定义命令多次Hook时,手动把`\UseHook'写入`\renewcommand'
\end{document}

image.png

第一处LogHook结果:

-> The generic hook 'cmd/foo/before':
> Code chunks:
>     ---
> Document-level (top-level) code (executed last):
>     -> extra 
> Extra code for next invocation:
>     ---
> Rules:
>     ---
> Execution order:
>     ---.

第二处LogHook结果,但因为不会第二次自动修补,进而不生效:

-> The generic hook 'cmd/foo/before':
> Code chunks:
>     ---
> Document-level (top-level) code (executed last):
>     -> extra extra 
> Extra code for next invocation:
>     ---
> Rules:
>     ---
> Execution order:
>     ---.

Happy Hooking!

撰写答案

请登录后再发布答案,点击登录

发布
问题

分享
好友

手机
浏览

扫码手机浏览