TransWikia.com

Define a macro that generates a macro that takes in arguments

TeX - LaTeX Asked by Kelvin Lee on December 4, 2020

I’ve used latex for quite some time now, but only limited to the very basics. I try to use packages whenever possible. I’m now trying to write some codes myself, in order to make my latex doc cleaner. This is what I want to achieve:

When I define:

symb{flow}{f_#1^#2}[a,b]

I want to be able to use

flow        % outputs $f_a^b$
flow[x,y]   % outputs $f_x^y$

Note that the number of indices must not necessarily be 2, it could be 1, or it could be more than 2.

The following is what I have now:

NewDocumentCommand{symb}{m m m}
{ expandafterNewDocumentCommandcsname#1endcsname{>{SplitList{,}}O{#3}}
    { 
        % Not sure what I need to write here
    }
}

Basically I want to be able to use symb{flow}{f_#1^#2}[a,b] to define a macro flow that takes in an optional argument, which is a comma-delimited indices of the variable. In the case where the optional argument is not provided, the default indices (in this case a, b) will be used.

In python, this would be written as:

def symb(expr, default):
    def fn(*args):
        if len(args) == 0:
            return expr % args
        else:
            return expr % default

    return fn

flow = symb('$f_%s^%s$', (a, b))

flow()        % outputs $f_a^b$
flow(x, y)    % outputs $f_x^y$

3 Answers

You can do it, but you should not take Python as a model for programming in LaTeX.

In LaTeX arguments are braced and cannot be substituted by comma lists.

Anyway, here's an implementation with any number of arguments in the template (but you of course have to specify how many you want).

documentclass{article}
usepackage{amsmath}
usepackage{xparse}

ExplSyntaxOn

NewDocumentCommand{symb}{mmmm}
 {% #1=name of command to define, #2=number of args, #3=template, #4=default

  % define the internal version
  cs_new_protected:cn { kelvin_symb_#1:prg_replicate:nn{#2}{n} } { #3 }

  % define the external version
  exp_args:Nc NewDocumentCommand { #1 } { O{#4} }
   {
    __kelvin_symb_do:nnx { #1 } { #2 } { clist_map_function:nN { ##1 } __kelvin_symb_brace:n }
   }
 }

cs_new:Nn __kelvin_symb_do:nnn
 {
  use:c { kelvin_symb_#1:prg_replicate:nn{#2}{n} } #3
 }
cs_generate_variant:Nn __kelvin_symb_do:nnn { nnx }

cs_new:Nn __kelvin_symb_brace:n { {#1} }

ExplSyntaxOff

symb{flow}{2}{f_{#1}^{#2}}{a,b}
symb{foo}{4}{int_{#1}^{#2}#3,d#4}{0,1,x,x}

begin{document}

$flow$

$flow[x,y]$

$foo$

$foo[a,b,f(x),x]$

end{document}

enter image description here

Some more words on the code. First the easy things:

exp_args:Nc NewDocumentCommand { #1 } { O{#4} }

is the expl3 version of

expandafterNewDocumentCommandcsname#1endcsname { O{#4 } }

and should be preferred.

Next a description of how symb works. First it defines an internal function with as many arguments as declared; its replacement text is provided by the given template. Thus symb{flow}{2}{f_{#1}^{#2}}{a,b} defines

kelvin_symb_flow:nn { f_{#1}^{#2} }

Note. There is no problem with the fact that _ is a letter in the scope of ExplSyntaxOn, because the declaration symb{flow}{2}{...}{...} is given outside that scope.

After that we define the user level command flow with an optional argument, whose default is the fourth argument to symb. This command indirectly calls the previously defined function by means of __kelvin_symb_do:nnx.

The first argument will be, in this case, flow, the second one is the number of arguments; their purpose is to be able to call the inner function. The last argument is the comma list (the default or the one given as optional argument to flow) but preprocessed so that it yields a list of braced items.

The normal version __kelvin_symb_do:nnn just forms the internal function kelvin_symb_flow:nn and unbraces the third argument. But we're using a variant thereof; when

clist_map_function:nN { #1 } __kelvin_symb_brace:n

is fully expanded (because of the x variant), if applied to a,b it yields {a}{b}. Thus we end up with

kelvin_symb_flow:nn { a } { b }

and LaTeX is happy to expand as usual to f_{a}^{b}.

Correct answer by egreg on December 4, 2020

For example, you can use this:

defsdef#1{expandafterdefcsname#1endcsname}

defsymb#1#2[#3,#4]{%
  sdef{#1}{expandafterfutureletexpandafternextcsname#1:aendcsname}%
  sdef{#1:a}{ifxnext[csname#1:bexpandafterendcsname 
              else     csname#1:bendcsname[#3,#4]fi}%
  sdef{#1:b}[##1,##2]{#2}%
}  

symb{flow}{f_#1^#2}[a,b]

$flow$ and $flow[x,y]$.

Second version of this macro implements your requirement from comments:

defsdef#1{expandafterdefcsname#1endcsname}
defaddto#1#2{expandafterdefexpandafter#1expandafter{#1#2}}

defsymb#1#2[#3]{%
  sdef{#1}{expandafterfutureletexpandafternextcsname#1:aendcsname}%
  sdef{#1:a}{ifxnext[csname#1:bexpandafterendcsname 
              else     csname#1:cendcsname #3,,,,,,,,end fi}%
  sdef{#1:b}[##1]{defparamslistA{#3,}defparamslistB{}setparams ##1,end,
     csname#1:cexpandafterendcsname paramslistB,,,,,,,,end}%
  sdef{#1:c}##1,##2,##3,##4,##5,##6,##7,##8,##9end{#2}%
}
defsetparams #1,{ifxend#1%
      expandafteraddtoexpandafterparamslistBexpandafter{paramslistA}%
   else expandafter setparamslist paramslistA end #1,%
   expandaftersetparamsfi}
defsetparamslist#1,#2end#3,{defparamslistA{#2}addtoparamslistB{#3,}}

symb{flow}{f_#1^#2}[a,b]

symb{test}{test: 1=#1, 2=#2, 3=#3, 4=#4}[a,b,c,d]

$flow$ and $flow[x,y]$.

test

test[mmm]

test[x,y,z]

This solution differs from egreg's solution: you need not expl3 cmplicated macros, only TeX primitives are used.

Answered by wipet on December 4, 2020

This is similar to wipet's but makes the argument to symb mandatory as discussed in comments

enter image description here

documentclass{article}

defsymb#1#2#3{%
expandafterdefcsname x#1endcsname##1##2{#2}%
expandafternewcommandcsname #1endcsname[1][#3]{%
  expandaftersplitcommacsname x#1endcsname ##1relax}}

defsplitcomma#1#2,#3relax{#1{#2}{#3}}

begin{document}

symb{flow}{f_#1^#2}{a,b}


$flow$

$flow[x,y]$

end{document}

And a version that allows multiple (up to 8) entries in the argument list.

enter image description here

documentclass{article}

defsymb#1#2#3{%
expandafterdefcsname x#1endcsname##1##2##3##4##5##6##7##8##9{#2}%
expandafternewcommandcsname #1endcsname[1][#3]{%
  expandaftersplitcommacsname x#1endcsname ##1,,,,,,,,,,relax}}


defsplitcomma#1#2,#3,#4,#5,#6,#7,#8,#9relax{#1{#2}{#3}{#4}{#5}{#6}{#7}{#8}{#9}relax}

begin{document}

symb{flow}{f_#1^#2}{a,b}

symb{flowb}{f_#1^#2g_#3^#4}{a,b,c,d}


$flow$

$flow[x,y]$

$flowb$

$flowb[x,y,w,z]$

end{document}

Answered by David Carlisle on December 4, 2020

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP