我试图理解 latex3 中变量的范围,并构建一个函数的示例,该函数采用命令的选项和参数,并且两者(选项和参数)具有相同的名称。
注意:我在示例中明确使用了相同的名称来找出并了解变量的范围。
更新:我知道我对两个不同的东西使用了相同的名称。这是故意的。这样做的原因是为了能够测试并了解哪些部分是封装的,哪些不是。据我所知,和 中有某种封装group_begin
。group_end
这种封装似乎也传播到组内调用的函数。但在我看来, 和 中keys_define
没有封装。
在调用中,\mycommand[name=optionname]{name=argname}
我们可以对两个不同的东西使用相同的名称。这在两个不同的东西中可能吗keys_define
?如果不行,技术原因是什么?
从其他编程语言的角度来看,我正在寻找类似mymodule_myoptions::l_mymodule_name_tl
和的东西mymodule_myargs::l_mymodule_name_tl
。
在我的例子中,我想调用\mycommand[name=optionname]{name=argname}
它keys_set
的各自的选项和参数,并使用选项(作为#1)和参数(作为#2)keys_define
调用一个函数,并且这个函数应该输出两者。\__mymodule_ShowOptionsAndArgs:nn #1 #2
name
name
option=optionname; arg=argname
下面的示例输出以下内容(另请参阅代码中的注释):
option=initoptionname; arg=initargname
option=optionname; arg=initargname
option=initoptionname; arg=argname
option=initoptionname; arg=initargname
我应该如何改变我的例子来得到
option=optionname; arg=argname
我该怎么做?最佳实践是什么?
\documentclass{article}
\ExplSyntaxOn
\keys_define:nn { mymodule / myoptions }
{
name .tl_set:N = \l_mymodule_name_tl,
}
\keys_define:nn { mymodule / myargs }
{
name .tl_set:N = \l_mymodule_name_tl,
}
\cs_new_protected:Npn \__mymodule_ShowOptionsAndArgs:nn #1 #2
{
\par option = #1 ;~ arg = #2
}
\cs_new_protected:Npn \__mymodule_myfunction:nn #1#2
{
\tl_new:N \l_optionname_tl
\tl_new:N \l_argname_tl
\tl_set:Nn \l_optionname_tl { initoptionname }
\tl_set:Nn \l_argname_tl { initargname }
\__mymodule_ShowOptionsAndArgs:nn { \l_optionname_tl } { \l_argname_tl }
% -> option=initoptionname; arg=initargname
\group_begin:
\keys_set:nn { mymodule / myoptions } { #1 }
\tl_set:Nn \l_optionname_tl { \l_mymodule_name_tl }
\__mymodule_ShowOptionsAndArgs:nn { \l_optionname_tl } { \l_argname_tl }
% -> option=optionname; arg=initargname
\group_end:
\group_begin:
\keys_set:nn { mymodule / myargs } { #2 }
\tl_set:Nn \l_argname_tl { \l_mymodule_name_tl }
\__mymodule_ShowOptionsAndArgs:nn { \l_optionname_tl } { \l_argname_tl }
% -> option=initoptionname; arg=argname
\group_end:
\__mymodule_ShowOptionsAndArgs:nn { \l_optionname_tl } { \l_argname_tl }
% -> option=initoptionname; arg=initargname
}
\NewDocumentCommand{\mycommand}{ o m }
{
\__mymodule_myfunction:nn { #1 } { #2 }
}
\ExplSyntaxOff
\begin{document}
\mycommand[name=optionname]{name=argname}
\end{document}
答案1
变量、控制序列、命令、宏……这些都可以在本地或全局进行定义或重新定义。如果更改是本地的,则其影响包含在当前 TeX 组中。
可以使用无数不同的语法变体/命令来开始和结束组,具体取决于上下文。以下列表仅用于说明一些最常见的构造。在每种情况下,“...”代表一个本地组。在此组内进行的本地更改在组结束后无效。
\group_begin:
...\group_end:
(expl3
)\group
...\endgroup
(文本){
...}
其中括号(花括号)不具有包含参数的功能。例如,\section{Section}
不会创建组,因为{
和}
只是界定参数。\begin{<environment>}
...\end{<environment>}
(LaTeX 2e)
如果您希望更改在当前组结束后仍然有效,则必须将其设置为全局更改。如果您希望更改仅影响当前组,则应将其设置为本地更改。
在中expl3
,变量应该用作本地变量或全局变量,并且它们的名称应该表明这一点。
\l_<module>_<description>_<type>
应仅在本地设置,例如\tl_set:Nn
用于令牌列表。\g_<module>_<description>_<type>
应该仅全局设置,例如\tl_gset:Nn
用于令牌列表。
\l_mymodule_name_tl
或之类的变量\g_mymodule_name_tl
不能同时用于两个不同的标记列表。如果您需要同时捕获两个不同的标记列表,则需要两个变量。
由于您希望每个name
键将标记列表变量设置为不同的标记列表,并同时访问这两个不同的标记列表,因此您不能对两个键使用相同的标记列表变量。相反,您需要类似
\keys_define:nn { mymodule / myoptions }
{
name .tl_set:N = \l_mymodule_myoptions_name_tl,
}
\keys_define:nn { mymodule / myargs }
{
name .tl_set:N = \l_mymodule_myargs_name_tl,
}
但是如果您希望更改在之后继续存在\group_end:
,则需要使用全局变量而不是局部\tl_gset:Nn
变量\tl_set:Nn
。
考虑
\documentclass{article}
\ExplSyntaxOn
\tl_new:N \g_optionname_tl
\tl_new:N \g_argname_tl
\tl_gset:Nn \g_optionname_tl { initoptionname }
\tl_gset:Nn \g_argname_tl { initargname }
\keys_define:nn { mymodule / myoptions }
{
name .tl_set:N = \l_mymodule_myoptions_name_tl,
}
\keys_define:nn { mymodule / myargs }
{
name .tl_set:N = \l_mymodule_myargs_name_tl,
}
\cs_new_protected:Npn \__mymodule_ShowOptionsAndArgs:nnnn #1 #2 #3 #4
{
\par option = #1 ;~ arg = #2 ; ~ oname = #3 ; ~ aname = #4
}
\cs_generate_variant:Nn \__mymodule_ShowOptionsAndArgs:nnnn { VVVV }
\cs_new_protected:Npn \__mymodule_myfunction:nn #1#2
{
\__mymodule_ShowOptionsAndArgs:VVVV \g_optionname_tl \g_argname_tl \l_mymodule_myoptions_name_tl \l_mymodule_myargs_name_tl
% -> option=initoptionname; arg=initargname
\group_begin:
\keys_set:nn { mymodule / myoptions } { #1 }
\tl_gset:NV \g_optionname_tl \l_mymodule_myoptions_name_tl
\__mymodule_ShowOptionsAndArgs:VVVV \g_optionname_tl \g_argname_tl \l_mymodule_myoptions_name_tl \l_mymodule_myargs_name_tl
% -> option=optionname; arg=initargname
\group_end:
\group_begin:
\keys_set:nn { mymodule / myargs } { #2 }
\tl_gset:NV \g_argname_tl \l_mymodule_aname_tl
\__mymodule_ShowOptionsAndArgs:VVVV \g_optionname_tl \g_argname_tl \l_mymodule_myoptions_name_tl \l_mymodule_myargs_name_tl
% -> option=optionname; arg=argname
\group_end:
\__mymodule_ShowOptionsAndArgs:VVVV \g_optionname_tl \g_argname_tl \l_mymodule_myoptions_name_tl \l_mymodule_myargs_name_tl
% -> option=optionname; arg=argname
}
\NewDocumentCommand{\mycommand}{ o m }
{
\__mymodule_myfunction:nn { #1 } { #2 }
}
\ExplSyntaxOff
\begin{document}
\mycommand[name=optionname]{name=argname}
\end{document}
键在本地设置\l_mymodule_myoptions_name_tl
和\l_mymodule_myargs_name_tl
,但我们使用值在全局设置\g_optionname_tl
和\g_argname_tl
。另一个区别是,本地变量最初为空,而全局变量则不是。
请注意,无论等如何,全局变化都会累积\group_begin:
,而局部变化则会在时消失\group_end:
。
答案2
在努力理解了底层概念之后,感谢这里发表的宝贵评论,我想到了一个可能的解决方案。该方法是使用属性列表实现的,并让其keys_define
键以子组名称为“前缀”添加:
\documentclass{article}
\ExplSyntaxOn
\prop_new:N \l_mypropertylist
\keys_define:nn { mymodule / options }
{ name .code:n = \prop_put:Nnn \l_mypropertylist { options / name } {#1}, }
\keys_define:nn { mymodule / args }
{ name .code:n = \prop_put:Nnn \l_mypropertylist { args / name } {#1}, }
\cs_new_protected:Npn \__mymodule_DoSomethingWithBothNameVariables:nn #1 #2
{ \par option = #1 ;~ arg = #2 }
\cs_new_protected:Npn \__mymodule_myfunction:nn #1#2
{
\keys_set:nn { mymodule / options } { #1 }
\keys_set:nn { mymodule / args } { #2 }
\prop_map_function:NN \l_mypropertylist \msg_show_item:nn
\__mymodule_DoSomethingWithBothNameVariables:nn
{ \prop_item:Nn \l_mypropertylist { options / name } }
{ \prop_item:Nn \l_mypropertylist { args / name } }
}
\NewDocumentCommand{\mycommand}{ o m }
{ \__mymodule_myfunction:nn { #1 } { #2 } }
\ExplSyntaxOff
\begin{document}
\mycommand[name=optionname]{name=argname}
\end{document}
输出为
> {options/name} => {optionname}
> {args/name} => {argname}
option=optionname; arg=argname
更新:这种方法的优点/特点之一是可以随时使用 轻松访问/报告模块的“状态”(即选项和参数的当前值)\prop_map_function:NN \l_mypropertylist <function>
。这是我在 中错过的一项功能/增强功能l3keys
:报告和访问定义的键的当前状态。据我所知,l3keys
仅提供\l_keys_path_str
、\l_keys_key_str
和 ,\l_keys_value_tl
它们仅存储最后处理的键。