如何在带有 expl3 的条件函数中使用变量?

如何在带有 expl3 的条件函数中使用变量?

我想学习expl3编程。为了熟悉语法,我想创建一些虚拟程序,例如计算素数。但是,我无法定义一个应该返回值的函数bool

我正在使用\prg_new_conditional:Nnn创建一个函数来测试某个数字是否为素数。当我尝试在此函数内声明变量(例如使用)时\int_set:Nn,我得到了一堆“缺失数字,视为零”错误。

\documentclass{article}

\usepackage{expl3}
\usepackage{xparse}

\ExplSyntaxOn

\cs_new:Nn \my_primes_up_to:n
  {
    \int_step_inline:nnn {2} {#1}
      {
        \bool_if:nTF { \my_is_prime_p:n { ##1 } }
          { ##1,~ }
          {}
      }
  }

\prg_new_conditional:Nnn \my_is_prime:n { p }
  {
    %\int_set:Nn \l_tmpa_int { 3 } This causes an error
    \int_if_odd:nTF { #1 } % This is just a dummy test
      { \prg_return_true: }
      { \prg_return_false: }
  }

\NewDocumentCommand{ \printPrimes }{ m }
  {
    {\noindent\Large\bfseries Primes~up~to~#1:\par\medskip}
    \my_primes_up_to:n {#1}
  }

\ExplSyntaxOff

\begin{document}

\printPrimes{18}

\ExplSyntaxOn
%\cs_meaning:N \my_is_prime_p:n
\ExplSyntaxOff

\end{document}

我做错了什么?我不太明白“谓词必须可扩展”是什么意思,也不太明白这意味着什么条件。

谓词是否仅用于简单测试,不能像 Java 布尔函数那样使用?如果是这样,那么编程此类行为的推荐方法是什么?我应该bool在内部函数中设置一个变量\bool_set吗?这需要调用内部函数并检查外部函数中的变量。或者有没有什么奇特的方法可以做到这一点?

也许我编写虚拟函数的方法不是最明智的,因为编程不需要这种行为LaTeX

此外,我非常感谢大家对expl3语法方面的其他错误或不常见用法提出的建议。这实际上是我的第一个程序 :)

答案1

中的布尔表达式expl3通过扩展工作,因此它们只能在实现中包含可扩展函数:这些函数在 中用星号标记interface3.pdf。最重要的是,赋值是不是可扩展:因此你不能用它们来实现谓词。实现不可扩展的分支测试,例如\tl_if_eq:nn(TF)使用\prg_new_protected_conditional:Nnn

答案2

Joseph Wright 已经指出你不能让 TeX在作为第一个参数
\int_set:Nn \l_tmpa_int { 3 } 处理的条件/宏内执行操作:\bool_if:nTF

\bool_if:nTF对处于扩展阶段的事物进行评估 — — 用 Donald E. Knuth 的比喻来说,TeX 是一个有眼睛、嘴巴和消化道的有机体,在 TeX 的食道中发生,就像一个“反刍”的过程 — — 并“期望”由此获得一个标记\prg_return_true:/ \prg_return_false:

\int_set:Nn意味着分配。分配不会在扩展阶段发生,而是在 TeX 处理的后期阶段发生,此时标记已经到达 TeX 的胃中。

扩展的同时还有调用\my_is_prime:n和传递的情况意味着除了其中一个标记/之外的例程还会遇到由 触发的形成赋值的不可扩展标记。\int_set:Nn\bool_if:nTF\my_is_prime:n\bool_if:nTF\prg_return_true:\prg_return_false:\int_set:Nn

interface3.pdf 包含注释

TeXhackers 注释:与 等不同,bool 数据类型不是使用\iffalse/基元实现的,在纯 TeX、LaTeX 2ε 等中。程序员不应根据对实现的任何特定期望来使用 bool 开关。\iftrue\newif

我还没有仔细研究布尔数据类型的实现。我猜想在扩展链中\bool_if:nTF内部使用\prg_return_true:/ ,在某个阶段应该会产生一些 TeX-\prg_return_false:⟨数字⟩-数量,并且由于形成触发的分配的不可扩展标记而被破坏\int_set:Nn,因此产生! Missing number, treated as zero.-错误。



expl3 旨在让您更轻松地控制扩展。因此,我很高兴看到\exp_args:Nno/ \exp_args:Nnf/... 函数,它可以与 结合使用\use:n
通过这种方式,您可以通过嵌套调用来操作应一个接一个放置的参数,就像放置在 之后的
\exp_args:Nno\use:n{...}{further to-be-manipulated argument}
参数一样。最内层表达式的参数 包含宏标记,用于放置在所有(从而应用于所有)被操作的参数之前。{...}\use:n
{...}\exp_args:Nno\use:n{...}{further argument}

例如,

\cs_new:Npn \foo {foo}
\cs_new:Npn \bar {bar}

代码

\exp_args:Nno\use:n{
  \exp_args:Nno\use:n{
    \macro
  }{\bar}
}{\foo}

产量:

\use:n{
  \exp_args:Nno\use:n{
    \macro
  }{\bar}
}{foo}

产量:

  \exp_args:Nno\use:n{
    \macro
  }{\bar}
{foo}

产量:

  \use:n{
    \macro
  }{bar}
{foo}

产量:

    \macro
  {bar}
{foo}


如果您坚持使用 expl3 而不是 Lua,那么为了好玩,您可以执行可扩展的 Eratosthenes 来枚举素数。

以下实现只是一个起点,需要优化:

  • 可处理的数字范围\int_eval:n不是那么大。
  • 发生平方后数字的范围会减小。
  • 出现分裂会使进程减慢。

正如我所说,这只是为了好玩。如果它只涉及\numexpr/可处理的数字范围\int_eval:n,那么在实际场景中,我绝对不会实现计算素数,而是实现从硬编码的表/列表中查找素数。

\ExplSyntaxOn

\cs_new:Nn \__UD_eratosthenes_mainloop:nnnn {
  % #1 - <remaining list of numbers from which primes shall be sieved>
  % #2 - <upper bound>
  % #3 - <list of primes already found>
  % #4 - <command for printing list of primes>
  \int_compare:nNnTF {(\tl_head:n {#1})*(\tl_head:n {#1})}>{#2}
  { \exp_end:#4{#3#1} }{
    \exp_args:Nnf \use:n { \__UD_eratosthenes_newlist:nnnwn {\use_i:nn} #1{!}!{}{#2} }
                         { \exp_args:Nnf \use:n {#3}{\tl_head:n {#1}} }{#4}
  }
}
\cs_new:Npn \__UD_eratosthenes_newlist:nnnwn #1#2#3#4!#5 {
  % #1 = \use_i:nn/\use_ii:nn - fork which test to perform
  % #2 = <prime number whose multiples are to be eliminated>
  % #3 = <smallest number from remaining list of numbers from which primes shall be sieved>
  % #4 = <remaining list of numbers from which primes shall be sieved>
  % #5 = <new list of numbers from which primes shall be sieved>
  \exp_args:Nf \str_if_eq:nnTF {\tl_head:n {#3}} {!}
  {\__UD_eratosthenes_mainloop:nnnn{#5}}{
    #1 {\int_compare:nNnTF {(#2)*(#2)}>{#3}\use_ii:nn\use_i:nn}
       {\int_compare:nNnTF {((#3)/(#2))*(#2)}={#3}}
    {
      #1{\__UD_eratosthenes_newlist:nnnwn{\use_ii:nn}{#2}{#3}}
        {\__UD_eratosthenes_newlist:nnnwn{#1}{#2}}#4!{#5}
    }
    {\__UD_eratosthenes_newlist:nnnwn{#1}{#2}#4!{#5{#3}}}
  }
}
\cs_new:Nn \__UD_eratosthenes_numberlist:n {
  \int_compare:nNnTF {#1} > {1} 
  { \exp_args:Nno \use:n {\exp_args:No \__UD_eratosthenes_numberlist:n } {\int_eval:n {#1-1}}{#1} }
  { \exp_end: }
}
\cs_new:Npn \primeslist #1#2 {
  \exp:w \exp_args:Nno \use:nn {\exp_end:} {
    % Hide things in braces because the user might supply #2/<tokens> that otherwise might
    % disturb tabulars/alignments etc.
    \exp:w  \int_compare:nNnTF {#1} < {2} 
            { \exp_end: #2{} }{
              \int_compare:nNnTF {#1} = {2} 
                               { \exp_end: #2{{#1}} }
                               {
                                 \exp_args:No \__UD_eratosthenes_mainloop:nnnn
                                           {\exp:w \__UD_eratosthenes_numberlist:n {#1}}
                                           {#1} {} {#2}
                               }
            }
  }
}
% \primeslist{<upper bound>}{<tokens>} after two expansion-steps yields:
% <tokens>{{2}{3}...{p_k; p_k < upper bound <= p_(k+1)}}

\cs_new:Npn \prettyprintcommand #1#2#3#4 {
  % #1 tokens before each item
  % #2 tokens behind each item
  % #3 tokens between items
  % #4 list of items formed by undelimied arguments
  %    Items must not contain "!" which is the case for decimal numbers represented by arabic numerals.
  \__UD_prettyprintloop:nnnnn{#1}{#2}{#3}{}#4{!}
}
\cs_new:Nn \__UD_prettyprintloop:nnnnn {
  \exp_args:Nf \str_if_eq:nnTF {\tl_head:n {#5}} {!}
  {}{  #4#1#5#2  \__UD_prettyprintloop:nnnnn{#1}{#2}{}{#3#4} }
}

\ExplSyntaxOff

\message{^^J}

\message{^^J\primeslist{1}{<tokens in front of prime-list>}}

\message{^^J}

\message{^^J\primeslist{2}{<tokens in front of prime-list>}}

\message{^^J}

\message{^^J\primeslist{100}{<tokens in front of prime-list>}}

\message{^^J}

\message{^^J\primeslist{100}{\prettyprintcommand{(}{)}{; }}}

\message{^^J}

\stop

终端输出:

<tokens in front of prime-list>{} 

<tokens in front of prime-list>{{2}} 


<tokens in front of prime-list>{{2}{3}{5}{7}{11}{13}{17}{19}{23}{29}{31}{37}{41
}{43}{47}{53}{59}{61}{67}{71}{73}{79}{83}{89}{97}} 


(2); (3); (5); (7); (11); (13); (17); (19); (23); (29); (31); (37); (41); (43);
 (47); (53); (59); (61); (67); (71); (73); (79); (83); (89); (97) 

答案3

你可以尝试我的functional包。它为 提供了直观的 LaTeX2 接口expl3,并且复合函数(包括条件)的求值是从内到外的,这与其他现代编程语言(如 )类似Lua

使用此包,每个函数都通过\Result命令传递其返回值。条件只是一个特殊函数,它返回\cTrueBool\cFalseBool

\documentclass{article}

\usepackage{functional}

\IgnoreSpacesOn

\PrgNewFunction \MyPrimesUpTo {m} {
  \TlClear \lTmpaTl
  \IntStepInline {2} {1} {#1} {
    \BoolVarIfTF {\MyIsPrime {##1}} {\TlPutRight \lTmpaTl {##1,~}} { }
  }
  \Result {\Value \lTmpaTl}
}

\PrgNewConditional \MyIsPrime {m} {
  \BoolSetTrue \lTmpaBool
  \IntStepInline {2} {1} {#1-1} {
    \IntCompareTF {\IntMathMod {#1} {##1}} = {0}
      { \BoolSetFalse \lTmpaBool } { }
  }
  \Result { \lTmpaBool }
}

\NewDocumentCommand \PrintPrimes {m} {
  {\noindent\Large\bfseries Primes ~ up ~ to ~ #1:\par\medskip}
    \MyPrimesUpTo {#1}
}

\IgnoreSpacesOff

\begin{document}

\PrintPrimes{18}

\end{document}

在此处输入图片描述

相关内容