对于原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
的钩子自动执行,在此时被延迟写入\foo
的extra
终于被自动修补进\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
.
问 \renewcommand 对 \AddToHook 的锚点重定义后、\AddToHook 的作用失效?