4

I want to learn expl3 programming. To make myself familiar with the syntax, I wanted to create some dummy programms, such as computing prime numbers. However, I am stuck defining a function which should return a bool value.

I am using \prg_new_conditional:Nnn to create a function which tests if a number is prime. When I try to declare variables inside this function e.g. with \int_set:Nn, I get a bunch of "Missing number, treated as zero errors".

\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}

What am I doing wrong? I don't quite understand what "predicates have to be expandable means" and what conditions this implies.

Are predicates just designed for simple tests and can't be used like e.g. a java boolean function? If so, what is the recommended way to programm such behaviour. Should I set a bool variable with \bool_set inside the inner function? That would require calling the inner function and checking the variable in the outer function. Or is there some fancy way to do this?

Maybe my approach to write dummy functions is not the most sensible, since such behaviour is not needed for LaTeX programming.

Also, I am very gratefull for every advice regarding other mistakes or uncommon usages regarding expl3 syntax. This is literally my first programm :)

5
  • Unrelated: you don't need to explicitly load expl3 if you have a newer LaTeX distribution.
    – Imran
    Jan 13, 2022 at 17:11
  • 1
    @Imran i know, but TeXStudio screams at me that is doesn't know \ExplSyntaxOn and such :) therefore i like loading expl3 and xparse
    – marv
    Jan 13, 2022 at 17:14
  • If you intend to do trial division: E.g., the Bertand-Chebyshew theorem (for every n>1 there is always at least one prime p such that n<p<2n) implies that for every integer n>2 the set of primes p; p<=(n-1) contains elements greater than or equal to sqrt(n). Thus: To determine whether n is prime, it is sufficient to divide n in ascending order by the elements of the set of primes p <=(n-1) (these were found in previous iterations) until either the square of the element is greater than n (in which case n is a prime) or n is dividable by the element (in which case n is a composite). Jan 13, 2022 at 23:22
  • @UlrichDiez thanks for the explanation. I also did some optimization such as only searching up to sqrt(n) to check whether n is prime. However, the point of this was just a dummy programm, not to write the most efficient prime sieve :)
    – marv
    Jan 14, 2022 at 6:57
  • @marv If it was about the most efficient prime sieve I would not elaborate on trial-division but on things like variations of the Atkin-sieve or the Eratosthenes-sieve. :-) Jan 14, 2022 at 19:47

3 Answers 3

1

Boolean expressions in expl3 work by expansion, and so they can only contain expandable functions in their implementation: those are marked with a star in interface3.pdf. Most importantly, assignments are not expandable: you therefore cannot use them to implement a predicate. You can implement non-expandable branching tests, for example \tl_if_eq:nn(TF), using \prg_new_protected_conditional:Nnn

2
  • Understand, it works now using the protected command. Could you explain what you mean with \tl_if_eq:nn(TF)?
    – marv
    Jan 13, 2022 at 17:29
  • Or was that just meant as an example command, which could be defined with \prg_new_protected_conditional:Nnn?
    – marv
    Jan 13, 2022 at 17:33
2

Joseph Wright already pointed out that you cannot have TeX do
\int_set:Nn \l_tmpa_int { 3 } within a conditional/macro that gets processed as \bool_if:nTF's first argument:

\bool_if:nTF evaluates things in the stage of expansion—which, using Donald E. Knuth's analogy of TeX being an organism with eyes, mouth and digestive-tract, takes place in TeX's gullet as a process of 'regurgitation'—and "expects" to hereby obtain one of the tokens \prg_return_true:/\prg_return_false:.

\int_set:Nn implies an assignment. Assignments do not take place during the stage of expansion but assignments take place in a later stage of TeX's processing, when tokens have already reached TeX's stomach.

The case of expansion of \my_is_prime:n also delivering \int_set:Nn while having \bool_if:nTF call \my_is_prime:n implies that the routine \bool_if:nTF besides one of the tokens \prg_return_true:/\prg_return_false: also encounters unexpandable tokens forming the assignment triggered by \int_set:Nn.

interface3.pdf contains the note

TeXhackers note: The bool data type is not implemented using the \iffalse/\iftrue primitives, in contrast to \newif, etc., in plain TeX, LaTeX 2ε and so on. Programmers should not base use of bool switches on any particular expectation of the implementation.

I did not yet study the implementation of the boolean-datatype carefully. I suppose \bool_if:nTF internally uses \prg_return_true:/\prg_return_false: within an expansion-chain which at some stage should yield some TeX-⟨number⟩-quantity and which is broken by the unexpandable tokens forming the assignment triggered by \int_set:Nn and which therefore yields ! Missing number, treated as zero.-errors.



expl3 is supposed to make things easier for you in regard to expansion control. Thereby I am glad about the \exp_args:Nno/\exp_args:Nnf/...-functions, which can be combined with \use:n.
With this you can manipulate arguments which should be placed one after the other by nesting calls like
\exp_args:Nno\use:n{...}{further to-be-manipulated argument}
in the argument {...} placed after \use:n.
The {...}-argument of the innermost \exp_args:Nno\use:n{...}{further argument}-expression contains the macro-token to place in front of all (and thus to apply to all) the manipulated arguments.

E.g., with

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

the code

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

yields:

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

yields:

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

yields:

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

yields:

    \macro
  {bar}
{foo}


If you insist in expl3 instead of Lua, for the sake of fun you can do expandable Eratosthenes for enumerating prime numbers.

The following implementation is just a starting-point and needs optimization:

  • The range of numbers processable by \int_eval:n is not that large.
  • Squaring occurs which decreases the range of numbers.
  • Division occurs which slows things down.

As I said this is for having fun. If it was about covering only the range of numbers processable by \numexpr/\int_eval:n, then in a real-live-scenario I'd definitely not implement calculating prime numbers but implement looking prime numbers up from a table/list where they are hard-coded.

\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

Terminal-output:

<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) 
2

You may try my functional package. It provides intuitive LaTeX2 interfaces for expl3, and the evaluation of composite functions (also conditionals) is from inside to outside, which is similar to other modern programmming languages such as Lua.

With this package, every function passes its return value with \Result command. And a conditional is just a special function, which return \cTrueBool or \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}

enter image description here

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .