Win11; TeXLive2024; VSCode; XeLaTeX.
对命令\foo
加了一个钩子:\AddToHook{cmd/foo/before}{...}
以追加特定文本,但在文中用\renewcommand
对\foo
重定义后、在使用\foo
命令,所生成的内容并未被追加该特定文本。
啥原因呢;怎样可以使钩子继续生效呢。
\renewcommand
之后再用一次相同的钩子:钩子的作用依旧无法生效\documentclass{article}
\newcommand{\foo}{target}
\AddToHook{cmd/foo/before}{extra }
\begin{document}
\foo
\renewcommand{\foo}{new target}
\foo
\end{document}
效果:
对于原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}
我来尝试理解一下每一步发生的过程(我希望我的理解没有大问题):
\foo
\AddToHook
由于Hook不会物理性地在导言区存在,而是会有一个延迟修补的机制,在此时增加extra
的内容尚且不会直接被写入\foo
的定义中begindocument
的钩子自动执行,在此时被延迟写入\foo
的extra
终于被自动修补进\foo
的定义中\foo
结果为extra target
因为对于通用钩子在使用\UseHookWithArguments
进行自动修补的同时会新建这个钩子,而钩子只能被\NewHook
一次,因此可以预见这种自动修补只会进行一次,这里在mark3的时刻,已经由通用钩子cmd/foo/begin
的自动修补机制实现了对cmd/foo/begin
的创建以及补充代码,故后续不会再进行自动修补。
\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
的用法,但这里最后一句的说法我猜就是『最佳实践』的方案)
\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}
第一处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!
@u10307 感谢雾月老师~
不过我没太看得懂。根据图中的提示,我是应该改成如下这样吗(虽然其满足需求了,但不懂会不会出现所说的“错误”):
@u64726 对的。
\renewcommand
那行后面还要加上%
。@u10307 我有两个小问题:
\UseHookWithArguments{cmd/foo/before}{0}
与\UseHook{cmd/foo/before}
的区别是什么?在这个例子里有什么区别吗?从语义上说,感觉后者会更好。\UseHook
,但您在评论中的『最佳实践』却指出对于\foo
的generic hook:cmd/foo/before
需要在\foo
内部显式使用\UseHookWithArguments
. 我不是太理解这种『最佳实践』为何看上去与文档想表达的含义似乎有违背(?)谢谢雾月老师~
@u70550
\UseHook
,后来新增了\UseHookWithArguments
,现在都统一用\UseHookWithArguments
了。\fancybox
的例子,只能显式地给出\UseHookWithArguments
,否则会出错。ltcmdhooks-doc.pdf 也有相同的例子。而且重定义以后只能显式地给出,否则不会执行钩子。只有这类通用命令钩子才建议这样做,一般都没必要显式给出。@u10307 这下对于您的总结我看的比较明白了,原文档比较大段的文本稍显难以理解,谢谢雾月老师。
@u10307 好滴明白,再次感谢雾月老师.