5

I am curious whether I have found a known distinction between testing for Blank versus testing for NoValue in a NewDocumentEnvironment where one parameter is Optional (default blank). The outputs from the two cases are different only for the TRUE branch in the two examples below. And only in the opening definition for the environment, not for the closing definition In the opening using the NoValue case, the output on my system (macOS) includes an extra spacing before the rule line.

\documentclass{article}

\NewDocumentEnvironment{NoticeB}{O{}}{%
    \IfBlankTF{#1}%
        {\noindent\rule{3cm}{1pt} \\}%
        {\noindent #1~\rule{3cm}{1pt} \\}%
    \noindent{}
    }
    {
    \\
    \IfBlankTF{#1}%
        {\noindent\rule{3cm}{1pt}}%
        {\noindent #1~\rule{3cm}{1pt}}%
    }

\NewDocumentEnvironment{NoticeNV}{O{}}{%
    \IfNoValueTF{#1}%
        {\noindent\rule{3cm}{1pt} \\}%
        {\noindent #1~\rule{3cm}{1pt} \\}%
    \noindent{}
    }
    {
    \\
    \IfNoValueTF{#1}%
        {\noindent\rule{3cm}{1pt}}%
        {\noindent #1~\rule{3cm}{1pt}}%
    }

\begin{document}

\begin{NoticeB}
Blank: These are the times that try men's souls.
\end{NoticeB}

\begin{NoticeNV}
No Value: These are the times that try men's souls.
\end{NoticeNV}

\begin{NoticeB}[+]
Blank +: These are the times that try men's souls.
\end{NoticeB}

\begin{NoticeNV}[+]
No Value +: These are the times that try men's souls.
\end{NoticeNV}

\end{document}

output from test

I recall some reading somewhere that touched on the distinctions in meaning. However, I do not recall whether one should be aware that one test does or does not behave as I have noticed.


(added by daleif)

Here is a small rewrite, that makes it easier to read the code, in relation to the additional question about a disappearing space

\documentclass{article}

\newcommand\marker[1]{%
  \IfBlankTF{#1}%
  {\noindent\rule{3cm}{1pt} \par}%
  {\noindent #1~\rule{3cm}{1pt} \par}%
}
\newcommand\markerX[1]{%
  \IfNoValueTF{#1}%
  {\noindent\rule{3cm}{1pt} \par}%
  {\noindent #1~\rule{3cm}{1pt} \par}%
}
\NewDocumentEnvironment{NoticeB}{O{}}{%
  \marker{#1}%
  \noindent{}%
}
{
  \\
  \marker{#1}%
}

\NewDocumentEnvironment{NoticeNV}{O{}}{%
  \markerX{#1}%
  \noindent{}%
}%
{
  \\
  \markerX{#1}%
}

\begin{document}

\begin{NoticeB}
Blank: These are the times that try men's souls.
\end{NoticeB}

\begin{NoticeNV}
No Value: These are the times that try men's souls.
\end{NoticeNV}

\begin{NoticeB}[+]
Blank +: These are the times that try men's souls.
\end{NoticeB}

\begin{NoticeNV}[+]
No Value +: These are the times that try men's souls.
\end{NoticeNV}

\end{document}

--

EDIT JJW

I realize the ambiguity in creating what could be construed as two questions.

  • What is the proper way to avoid unforeseen differences in the behavior of \IfBlankTF versus \IfNoValueTF? This question is answered in the two current replies.

  • Why does \IfNoValueTF give a blank space at the start of an environment but not at the end of the environment for the same apparent input conditions? This question is answered in the comments.

16
  • 3
    \IfNoValueTF is designed for use with o not O. It tests for a special no-value token which the code produces if an o-type argument is omitted. the absence of an optional argument is not the same as the presence of either a blank or empty argument.
    – cfr
    Commented Aug 4 at 18:33
  • 2
    I agree with cfr. See it in terms of NULL, o has an initial value of NULL, and \foo and \foo[] are then not the same as the first give #1 = NULL the other sets #1 = "". But O{} always sets #1 = "" initially, so a test against NULL would make no sense.
    – daleif
    Commented Aug 4 at 18:38
  • 2
    It disapears as ~ sits at the start of the line and gets descarded. Test with ~\\~A, A has no space. So the question is actually the opposite, why is the ~ not ignored at the start of the env.
    – daleif
    Commented Aug 4 at 19:10
  • 1
    both ~\noindent and \\~\noindent are incorrect input, so the output is a bit arbitrary anyway. but \\ ignores following spaces by design. Commented Aug 4 at 21:22
  • 1
    @JeffreyJWeimer yes the space goes after \` as white space is discarded after a line break, it would be discarded in vertical mode at the start of a paragraph too, but the paragraph is already started by \noindent` in the begin code. (the \noindent in teh end code does nothing as it is within the paragraph) Commented Aug 4 at 22:34

4 Answers 4

2

In LaTeX 2ε the arguments of macros and environments either can be optional or can be mandatory.

Mandatory arguments of a macro/of an environment are arguments that have to be provided when calling the macro/the environment. If with a macro-call/an environment-call a mandatory argument is to be empty/is to consist of no token at all or is to consist of more than one token or is to have leading space tokens that would be discarded instead of being tokenized when TeX's reading-apparatus is in state S (skipping blanks) or if it is to have a leading token which otherwise would erroneously mark the begin of an optional argument or the delimiter of a delimited argument, then it needs to be provided nested between a pair of matching curly braces/between a pair of matching explicit character tokens of category 1(begin group) resp. 2(end group). Such braces are stripped off/dropped/thrown away in the course of grabbing the tokens that with the macro-call/environment-call form the argument.

Optional arguments of a macro/of an environment are arguments that, when calling the macro /the environment, can/may be provided but don't have to be provided.

In case with the macro-call/environment-call an optional argument is not provided and thus a value for the corresponding macro's/environment's definition's ⟨replacement text⟩'s placeholder (one of #1 or #2 or #3 or #4 or #5 or #6 or #7 or #8 or #9) for the tokens that when calling the macro/the environment form the argument is not provided, a default value is used/default tokens are used.

Optional arguments of types O{⟨default⟩} and o in macro-calls/environment-calls are to be preceded by a delimiter [12 and themselves are delimited at the right by a delimiter ]12. (12 denotes the category code 12(other).) These delimiters are stripped off/are dropped/are thrown away in the course of grabbing the tokens that form the argument. A pair of matching explicit character tokens of category 1(begin group) resp. 2(end group), usually curly braces {1}2, surrounding everything that is between [12 and ]12, if present, is stripped off/is dropped/is thrown away also in the course of grabbing the tokens that form the optional argument. This way you can nest optional arguments inside optional arguments without the ]12-delimiter of an innermore optional argument erroneously be taken for the ]12-delimiter of an outermore optional argument.

With an O{⟨default⟩}-type-argument the user provides the default value/the default tokens at the time of via \NewDocumentCommand defining the macro/at the time of via \NewDocumentEnvironment defining the environment.

E.g., with O{\Foo\Bar\Baz} the default for the optional argument is the token sequence \Foo\Bar\Baz and with a macro-call/with an environment-call where the optional argument is not provided you get the tokens \Foo\Bar\Baz for the macro's/environment's definition's ⟨replacement text⟩'s placeholder (one of #1 or #2 or #3 or #4 or #5 or #6 or #7 or #8 or #9) of that O-type-argument.

E.g., with O{} the default for the optional argument is emptiness/is no tokens at all and with a macro-call/with an environment-call where the optional argument is not provided you get emptiness/no tokens for the macro's/environment's definition's ⟨replacement text⟩'s placeholder (one of #1 or #2 or #3 or #4 or #5 or #6 or #7 or #8 or #9) of that O-type-argument.

With o-type-arguments the user does not provide a default at the time of defining the macro/the environment.

An o-type-argument is like an O{⟨default⟩}-type-argument where at the time of defining the macro/the environment as ⟨default⟩ a sequence of explicit character tokens is specified which cannot easily be produced via having LaTeX tokenize ordinary user-input, namely the sequence -11, N11, o11, V11, a11, l11, u11, e11, -12. Note that the leading -11 is of category 11(letter) while the trailing -12 is of category 12(other). Let's call that sequence of explicit character tokens the "no-value-marker".
(The tokens forming the no-value-marker are accessible via the expl3 programming layer as the content of a token-list-constant whose name is \c_novalue_tl. That token list is documented in section "53.2 Constant token lists" of "The LaTeX3 Sources", released 2024-07-20 by The LaTeX Project.)

The tests \IfNoValueT, \IfNoValueF and \IfNoValueTF are to be used for testing whether a sequence of tokens equals that no-value-marker.

These tests are intended to be used with o-type arguments.

The logic behind this is that with an o-type-argument in the case of the macro/environment-definition's ⟨replacement text⟩'s placeholder for that o-type-argument holding only a sequence of tokens which equals the sequence of tokens that makes up the no-value-marker it is assumed that the default is used because no optional argument is provided at all with the macro-call/environment-call.

So these tests are intended for testing whether and forking depending on whether with a call to a macro/to an environment an optional argument of type o was provided at all. Usually you cannot use an o-type-argument's default for anything sensible but for doing such testing and forking by means of the \IfNoValueT/\IfNoValueF/\IfNoValueTF-tests. Therefore when writing definitions for macros or environments which process o-type-arguments it is sort of mandatory to place code into the ⟨replacement-text⟩ for cranking out via \IfNoValueT/\IfNoValueF/\IfNoValueTF whether those arguments hold the default/the no-value-marker or whether those arguments hold something else.

In the last but one paragraph the wording "it is assumed" is used because the test can be fooled by explicitly providing the no-value-marker as optional argument. But that requires some trickery and applying such trickery implies that one is knowing what one is doing and is not doing it accidentally but is doing it deliberately.

In macro-definitions via \NewDocumentCommand and in environment-definitions via \NewDocumentEnvironment you use an O{⟨default⟩}-type-argument when you want TeX to basically behave the same way no matter if an optional argument is or is not explicitly provided with the macro-call/environment-call, in the case of an optional argument not being provided assuming the argument having a default-value/holding the ⟨default⟩-tokens.
In macro-definitions via \NewDocumentCommand and in environment-definitions via \NewDocumentEnvironment you use o-type-arguments and \IfNoValueT/\IfNoValueF/\IfNoValueTF-tests when you want to fork/to implement different behavior depending on whether an optional argument was or was not explicitly provided with the macro-call/environment-call.

The tests \IfBlankT, \IfBlankF and \IfBlankTF are for testing whether it is (not) the case that a sequence of tokens either has no tokens at all or consists only of explicit space tokens. (A so-called explicit space token is an explicit character token of category 10(space) whose character code is 32; the character code 32 denotes the number of the code point of the space character in TeX's internal character representation scheme, which with traditional TeX engines like Donald E. Knuth's TeX and Hàn Thế Thành's pdfTeX is ASCII and with LuaTeX- and XeTeX-engines is unicode.)

You ask why with calling the environment NoticeNV as in

\begin{NoticeNV}
No Value: These are the times that try men's souls.
\end{NoticeNV}

you get a horizontal space at the left of the first horizontal rule.

Let's look at the definition of the environment NoticeNV:

\NewDocumentEnvironment{NoticeNV}{O{}}{%
    \IfNoValueTF{#1}%
        {\noindent\rule{3cm}{1pt} \\}%
        {\noindent #1~\rule{3cm}{1pt} \\}%
    \noindent{}
    }
    {
    % the following \\ does precede the result of the following test.
    \\
    \IfNoValueTF{#1}%
        {\noindent\rule{3cm}{1pt}}%
        {\noindent #1~\rule{3cm}{1pt}}%
    }

That environment does process an optional argument whose default is emptiness.

With the call above no optional argument is provided. Thus the default, i.e. emptiness, is used in the place of the environment's ⟨replacement-text⟩'s argument-placeholder #1.

Thus the test

    \IfNoValueTF{#1}%
        {\noindent\rule{3cm}{1pt} \\}%
        {\noindent #1~\rule{3cm}{1pt} \\}%

, i.e., checking whether #1 consists of those tokens that form the no-value-marker, is applied, while #1 is empty while emptiness in turn is not the same as the special no-value-marker.

Thus TeX ends up doing the F-branch, which is \noindent #1~\rule{3cm}{1pt} \\ with #1 being empty.
I.e., TeX ends up doing \noindent ~\rule{3cm}{1pt} \\.

Notice that in your example defining the environment NoticeNV takes place while LaTeX is not switched to expl3-mode. Therefore ~ denotes a non-break-space.

Now the question is why with the begin of the environment, at the left of the horizontal rule at the top you get that additional indenting of the width denoted by a space token while space factor is 1000 while with the end of the environment, at the left of the horizontal rule at bottom, you don't get such indenting although the test is the same and yields the same result/the same tokens, namely the tokens
\noindent~13\rule{1312c11m11}2{1112p11t11}210\\?

The subtle point is:

At the start of the environment TeX is in vertical mode and has not yet started a new paragraph.

At the end of the environment the tokens forming the test-result are preceded by \\, i.e., are preceded by directive for a line break within a paragraph whereafter TeX is in horizontal mode.

Let's look at ~:

Active ~, i.e., ~13 in LaTeX 2ε usually is defined to be a macro which expands to
\leavevmode\nobreak\␣.
\leavevmode in turn expands to \unhbox\voidb@x.
\nobreak in turn inserts a penalty of value 10000 which is sort of infinitely large for TeX.

So \noindent~13 is like \noindent \unhbox\voidb@x \penalty112012012012012\relax \␣.

Notice the control-space, \␣, at the end?

The TeXbook says a few things about the control-space:

In "Chapter 25: Summary of Horizontal Mode" you find:

\␣ . A control-space command appends glue to the current list, using the same amount that a ⟨space token⟩ inserts when the space factor is 1000.

In "Chapter 13: Modes", somewhere right after exercise 13.1, you find:

A control space (\␣) will, however, be regarded as the beginning of a paragraph; the paragraph will start with a blank space after the indentation.

That's why at the begin of the environment, which is also the beginning of the paragraph, you get that additional indenting.

In "Chapter 14: How TeX Breaks Paragraphs into Lines" you find:

When a line break actually does occur, TeX removes all discardable items that follow the break, until coming to something non-discardable, or until coming to another chosen breakpoint.

\\ causes a linebreak within a paragraph while \noindent does nothing in horizontal mode and all things coming from ~ are either void or discardable.
That's why at the end of the environment the space/horizontal glue coming from ~ is discarded and you don't get the additional indenting.

7
  • 1
    Thank you for the fullest explanation of everything!!! Commented Aug 8 at 20:03
  • 1
    oh no someone stole my +15 :-) Commented Aug 8 at 21:46
  • @JeffreyJWeimer Sorry - somehow I missed delivering an explanation for the most interesting aspect of the question: Why indentation at the begin of the environment, but not at the end of the environment. I edited my answer and added some explanation. Commented Aug 9 at 18:31
  • 1
    @DavidCarlisle I didn't intend this to happen. For a first attempt at doing some compensation I upvoted some contributions of yours. Of course, I am also happy to make amends in any other way you think is best. :-) Commented Aug 9 at 18:55
  • 1
    @UlrichDiez it really doesn't matter: I am not exactly in need of the points:-) Commented Aug 9 at 18:56
6

The two tests are testing completely different things

\IfBlankTF is testing if the argument is empty or only has space characters.

\IfNoValue (used with an o argument) tests if the argument was used.

Even with an optional argument you may want to separately test for both these things. For example

\begin{table}[]

means don't allow the float anywhere (latex gives a warning about this) \IfNoValueTF would be false as the option is used, but \IfBlankTF would be true as it is empty (blank).

Conversely

\begin{table}

means use the default placement, and \IfNoValueTF would be true in this case as the option is not used.

Note that if (as in the example in the question) you use O to supply a default value, rather than o, then there is always a value, and \IfNoValueTF will always be false.

6
  • I appreciate the distinct clarification on Blank versus NoValue, especially with clear examples and the warning to test both for absolute certainty. Thanks! Commented Aug 4 at 21:06
  • @JeffreyJWeimer I would only test an optional argument for being empty in order to raise an error, don't treat [] as equivalent to the option not being used. Commented Aug 4 at 21:11
  • > don't treat [] as equivalent to the option not being used. ... certainly! Commented Aug 4 at 21:58
  • @JeffreyJWeimer yes, but \IfValueTF is only useful for an optional argument when you don't specify a default i.e. not useful in the examples in your question at all.
    – cfr
    Commented Aug 4 at 23:13
  • @cfr yes maybe I should have highlighted that. I directly answered the question in the title here and didn't really look at the example given as that seemed to mostly obscure the issue (cf the side discussion on ~) I'll add something to the answer and clean up some of the comments Commented Aug 4 at 23:17
4

The legacy \newcommand allows for defining a command taking a leading optional argument, which one should set a default value of. Thus

\newcommand{\foo}[2][default]{#1-#2}

\foo{xyz}

\foo[aaa]{xyz}

would produce

default-xyz
aaa-xyz

There is no real way to distinguish between presence or absence of the optional argument. A common technique was to set an empty default value and do something like

\newcommand{\foo}[2][]{%
  \if\relax\detokenize{#1}\relax
    <code for no optional argument, using #2>%
  \else
    <code for nonempty optional argument using #1 and #2>%
  \fi
}

but the calls \foo{abc} and \foo[]{abc} would be the same.

This is corrected by \NewDocumentCommand that allows two optional argument types, namely o and O{<default>}. With

\NewDocumentCommand{\foo}{om}{%
  \IfNoValueTF{#1}{%
    <code for no optional argument using #2%
  }{%
    <code for nonempty optional argument using #1 and #2>%
  }%
}

the calls \foo{xyz} and \foo[]{xyz} would be different, because the latter would use the “false” branch, as the optional argument is present, albeit empty.

The LaTeX kernel also offers \IfBlankTF, but it shouldn't be used for optional argument, or you'd emulate the old behavior, which could well be deemed inconsistent.

Use it only when you really want to test for an empty argument (optional or not). Or if you want to emulate the inconsistent behavior of \newcommand.

The behavior of \NewDocumentEnvironment is completely analogous.

Your call

\begin{NoticeNV}
No Value: These are the times that try men's souls.
\end{NoticeNV}

will pass the default empty value for the optional argument, so \IfNoValueTF{#1} would return false, because “empty” is not the same as “-NoValue-”.

To be more explicit, if you do

\NewDocumentCommand{\foo}{om}{<code>}

the call \foo{xyz} would pass the special string -NoValue- as #1. With

\NewDocumentCommand{\foo}{O{}m}{<code>}

the call \foo{xyz} would pass an empty argument as #1.

1
  • Thanks for the insights into the pitfalls to avoid for transition from legacy to new code constructions. I especially appreciate the last two cases to clarify o versus O as parameters. Commented Aug 4 at 22:18
1

For those interested, the approaches with \IfNoValueTF versus \IfBlankTF are below.

\documentclass{article}

\NewDocumentEnvironment{NoticeNV}{o}{%
    \IfNoValueTF{#1}%
        {\noindent\rule{3cm}{1pt} \\}%
        {\noindent #1~\rule{3cm}{1pt} \\}%
    \noindent{}
    }
    {
    \\
    \IfNoValueTF{#1}%
        {\noindent\rule{3cm}{1pt}}%
        {\noindent #1~\rule{3cm}{1pt}}%
    }

\NewDocumentEnvironment{NoticeB}{O{}}{%
    \IfBlankTF{#1}%
        {\noindent\rule{3cm}{1pt} \\}%
        {\noindent #1~\rule{3cm}{1pt} \\}%
    \noindent{}
    }
    {
    \\
    \IfBlankTF{#1}%
        {\noindent\rule{3cm}{1pt}}%
        {\noindent #1~\rule{3cm}{1pt}}%
    }

\begin{document}

\begin{NoticeB}[]
Blank: These are the times that try men's souls. \\
CORRECT CONSTRUCTION
\end{NoticeB}

\begin{NoticeNV}
No Value: These are the times that try men's souls. \\
CORRECT CONSTRUCTION
\end{NoticeNV}

\begin{NoticeNV}[]
Blank in No Value: These are the times that try men's souls. \\
INCORRECT CONSTRUCTION
\end{NoticeNV}

\begin{NoticeB}
No Value in Blank: These are the times that try men's souls. \\
INCORRECT CONSTRUCTION (but appears to work)
\end{NoticeB}

\end{document}

outputs

Lessons learned!

3
  • the fourth case is just equivalent to the first one, so it makes no sense to say one is correct and one not. in case 4, the code receives exactly the same argument as in case 1. the [] in case 1 is unnecessary because it is just specifying the default value anyway.
    – cfr
    Commented Aug 9 at 19:30
  • @cfr Perhaps "improper" or "illogical" construction would be a better phrasing. In any case, I leave as is if only to help those who may struggle with which ones to use and avoid. Commented Aug 10 at 22:54
  • is neither. it is perfectly correct, proper and logical. it is literally how default arguments work. if you set up a macro to take an optional argument with default <value> and you don't give an optional argument when you use it, you get <value>. there is nothing weird here. it's exactly the same if you use \newcommand\mymacro[1][<value>]{...} and then say \mymacro. for the fourth case to be incorrect, you'd need to have set up a mandatory argument or something. not using an optional argument has to be fine by definition.
    – cfr
    Commented Aug 10 at 23:59

You must log in to answer this question.

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