4

I'd like to rename arguments like #1 into something like \ies@firstStar to have less error prone programming and less variable renaming when changing the parameters of a function. However doing something like \def\ies@firstStar{#1} fails, any idea what's wrong?

\documentclass{article}
\usepackage{xparse}

\makeatletter \NewDocumentCommand{\myTest}{s}{ \bgroup% make definitions local \def\ies@firstStar{#1}% \IfBooleanTF{\ies@firstStar}{% One star }{% No star } \egroup }

\makeatother

\begin{document} \myTest \end{document}

tobiasBora
  • 8,684
  • I'm not sure about the reason of such indirection. Why not using directly \IfBooleanTF{#1}? This seems like an XY-question; can you please give a better example that justifies the approach? – egreg Oct 01 '21 at 12:04
  • @egreg Because after I use #1 everywhere in the function. And first I will soon forget what #1 is (which is error prone), and second if I add a single new parameter at the beginning of my function definition I need to redefine ALL my variables... I already think TeX is the most obfuscated language ever, no reason to add more mysterious numbers ;-) – tobiasBora Oct 01 '21 at 12:07
  • @egreg Putting stuff like #1 into a variable is not completely unreasonable, especially if you are going to program something where the value of this variable might change along the way. In most non-TeX languages, it is, after all, quite common to use many auxiliary local variables in function definitions. – Gaussler Oct 01 '21 at 12:09
  • @tobiasBora Sorry, I can agree with nothing you're saying. You're misusing the tools. – egreg Oct 01 '21 at 12:21
  • 1
    @egreg It could be, but then I'm curious to see how I could use the tools better. Like in python, if I define a function def myfunction(lastname,firstname) and suddently decide that it should be def myfunction(firstname,lastname), I have a single line to change. In LaTeX, I have all the code of the function to update. And don't you agree that lastname is waaay clearer that #1 later in the code? – tobiasBora Oct 01 '21 at 12:24
  • @tobiasBora No, I don't agree. But, hey, I've been programming in TeX for 30+ years. Python and TeX are different languages. It's useless to try forcing one being like the other. – egreg Oct 01 '21 at 12:38
  • 1
    @egreg I actually agree with tobiasBora to some extent about the fact that the TeX syntax is strange and alien, and I, too, would prefer named parameters to numbered ones. And if I use any other programming language, I use local variables inside function definitions all the time. But in TeX, and only in TeX, this is considered dangerous and bad style (and makes your macro unexpandable if it was not already). – Gaussler Oct 01 '21 at 12:43
  • @Gaussler Use Lua… ;-) – egreg Oct 01 '21 at 12:56
  • @egreg Perhaps that’s the solution. ;-) – Gaussler Oct 01 '21 at 12:56

3 Answers3

3

Internally, I’m not sure exactly how \IfBooleanTF works, but the following solves the problem. However, at least for this simple example, I fail to see how this makes the code more readable.

\documentclass{article}
\usepackage{xparse}

\makeatletter \NewDocumentCommand{\myTest}{s}{ \begingroup% better to use this than \bgroup...\egroup \def\ies@firstStar{#1}% \expandafter\IfBooleanTF\expandafter{\ies@firstStar}{% One star }{% No star } \endgroup }

\makeatother

\begin{document} \myTest \myTest* \end{document}

enter image description here

Gaussler
  • 12,801
3

Use \let instead of \def to avoid the \expandafters. In the case of \def, the replacement text of \ies@firstStar is \BooleanFalse or \BooleanTrue. In the case of \let, \ies@firstStar actually is equivalent to \BooleanFalse or \BooleanTrue.

As Gaussler notes, \let can only assign a single token. Therefore, this approach works because the s argument will never be more than a single token.

\documentclass{article}
\usepackage{xparse}

\makeatletter \NewDocumentCommand{\myTest}{s}{% \begingroup% make definitions local \let\ies@firstStar=#1% \IfBooleanTF{\ies@firstStar}{% One star% }{% No star% }% \endgroup }

\makeatother

\begin{document} \myTest \myTest* \end{document}

enter image description here

  • As in my answer, you might want to change \bgroup...\egroup to \begingroup...\endgroup. – Gaussler Oct 01 '21 at 12:11
  • @Gaussler While I generally do use begin/end versions, I'm not sure of the difference in this case. Nonetheless, I gladly changed it. – Steven B. Segletes Oct 01 '21 at 12:13
  • Well, in math mode, it makes a difference. – Gaussler Oct 01 '21 at 12:15
  • @Gaussler That is true that, in math mode, a difference can arise between \begingroup and \bgroup, though in the case of detecting a star version, I'm not sure it amounts to any real risk. – Steven B. Segletes Oct 01 '21 at 12:16
  • 1
    It should also be remarked that this only works because, in this case, the value of #1 is a single token. If you do it with an m-type argument, you’re gonna be in trouble. – Gaussler Oct 01 '21 at 12:20
  • Actually, it seems the value is something that expands to some variant of \Gamma or \Delta, depending on the value. Not sure why. Didn’t test it in detail. – Gaussler Oct 01 '21 at 12:21
  • 1
    @Gaussler It's that the standard TeX fonts have \Gamma and \Delta in slots 0 and 1, but that's really only reflecting that the values were never meant to be typeset – Joseph Wright Oct 01 '21 at 12:23
  • @StevenB.Segletes You are introducing spurious spaces. Remove the one in #1 % – egreg Oct 01 '21 at 12:36
  • @egreg Thank you. I guess I was of the false understanding that you were allowed one "free" space after \let assignments. – Steven B. Segletes Oct 01 '21 at 12:39
  • 1
    There can be optional spaces around the (optional) =. – egreg Oct 01 '21 at 12:40
  • "Therefore, this approach works because the s argument will never be more than a single ." that implies #1 could be or nothing (\let would not work in that case) it just works becase it is always a single token \BooleanFalse or \BooleanTrue – David Carlisle Oct 01 '21 at 13:53
  • @DavidCarlisle Thank you for the correction. – Steven B. Segletes Oct 01 '21 at 16:10
2

It's difficult to understand the need for such an indirection.

Anyway, the current implementation of the s argument type is that, if s is the first argument, the value assigned to #1 in case no * comes along is \BooleanFalse, \BooleanTrue if there is a *.

Then the current implementation of \IfBooleanTF{#1}{True}{False} chooses the true or false branch according on whether #1 is \BooleanTrue or \BooleanFalse. Next you might want to know that currently \BooleanTrue and \BooleanFalse are \chardef tokens, so they're not expandable.

But the user/programmer should not rely on this. In future releases this might change; not likely to happen, but, hey! Who knows?

Besides, I can't see any advantage whatsoever in \IfBooleanTF{\ies@FirstStar} over \IfBooleanTF{#1}. To the contrary, I see several disadvantages, for instance that you need grouping to avoid timing expansions or macro clobbering.

Why doesn't your \def work? Because \ies@FirstStar is not the same as \BooleanTrue or \BooleanFalse: it is a macro expanding to either of them (and expl3 uses \ifx for the check, not \if which wouldn't work, so no expansion is performed). If you use, as suggested, \let instead of \def, the code seems to work, but it might stop to with a future release of expl3. On the contrary, \IfBooleanTF{#1} is guaranteed to work forever.

egreg
  • 1,121,712
  • “Forever” is a very big promise. Not sure how it’ll work after the Sun expands and eats up the Earth. ;-) Also, I think the \expandafter solution is also guaranteed to work forever. – Gaussler Oct 01 '21 at 12:38
  • Thanks for the clarification, I understand better now the reason of the fail. That said, talking only about readability/maintainability/debugging, and not about TeX internals, if you have just a single parameter, sure it is not that of a problem to keep #1. Now, when you have multiple parameters (including multiple parameters which come with a BooleanFalse/BooleanTrue definition, which is my case), codes with #n are very hard to read and maintain, I guess you can agree on that. Also, is there any other edge case of using \let? – tobiasBora Oct 01 '21 at 12:45
  • @tobiasBora you can almost never use \let\foo=#1 it just works in the specific implementation of the s type as #1 is always exactly one token (and never two tokens or empty) so let failing isn't an edge case it is the expected result. – David Carlisle Oct 01 '21 at 14:10
  • 1
    @tobiasBora Whilst it is the case that a lot of languages use named parameters, TeX doesn't. That means it's hard to get away from knowing how to use them. In particular, assignments prevent working in an expansion context: in those places, you have to use #1, etc. I guess I'd say this is a feature of the language one has to learn to live with. – Joseph Wright Oct 01 '21 at 16:25
  • 1
    @tobiasBora You may want to look at https://tex.stackexchange.com/a/549462/4427 – egreg Oct 01 '21 at 16:42