latex3 / mathtools

Mathematical tools to use with amsmath
LaTeX Project Public License v1.3c
55 stars 6 forks source link

[Feature request/bug?] Allow empty left or right delimiter for \DeclarePairedDelimiter{,X,XPP} #41

Closed 6601618158 closed 8 months ago

6601618158 commented 2 years ago

Currently, if I define something like \DeclarePairedDelimiterXPP{\restrict}[2]{}{.}{\rvert}{_{#2}}{#1} % ⟼ \left. #1\right\rvert_{#2} to get a macro for function restriction, I get the expected result (see comment) only if I use \restrict*{X}{Y}, \restrict[\big]{X}{Y} or some other size command. If I just use \restrict{X}{Y}, I get a . as the left delimiter. Leaving left_delim empty in my macro definition results in a Missing delimiter (. inserted) error.

Would it be possible to add support for this? Maybe I'm abusing the system here (so I'd be OK with a ‘won't fix’), so I'm not actually sure whether this is a bug report or a feature request… :thinking:

There's also an older tex.stackexchange question about this (link) with two different workarounds, so there seems to be some demand for this.

davidcarlisle commented 2 years ago

You can I think do this. This does the test for . each time. It would be more efficient to just test at definition time and adjust the internal wrapper in that case not to use the delimiter but that would I think require more code re-arrangement.

\documentclass{article}
\usepackage{mathtools}

\MHInternalSyntaxOn
\renewcommand\MT_delim_default_inner_wrappers:n [1]{
   \@namedef{MT_delim_\MH_cs_to_str:N #1 _star_wrapper:nnn}##1##2##3{
      \mathopen{}\mathclose\bgroup ##1 ##2 \aftergroup\egroup ##3
    }
    \@namedef{MT_delim_\MH_cs_to_str:N #1 _nostarscaled_wrapper:nnn}##1##2##3{
      \mathopen{##1}##2\mathclose{##3}
    }
    \@namedef{MT_delim_\MH_cs_to_str:N #1 _nostarnonscaled_wrapper:nnn}##1##2##3{
      \ifx.##1\else\mathopen##1\fi##2\ifx.##3\else\mathclose##3\fi
    }
  }
\MHInternalSyntaxOff

\DeclarePairedDelimiterXPP{\restrict}[2]{}{.}{\rvert}{_{#2}}{#1}

\begin{document}

$\restrict{X}{Y}$

$\restrict*{X}{Y}$

$\restrict[\Big]{X}{Y}$

\end{document}
blefloch commented 2 years ago

Someone might want "." as a delimiter. I think it would make more sense to use an empty argument to denote no delimiter here, and test for empty argument (at definition time or run time).

davidcarlisle commented 2 years ago

@blefloch yes I wondered about using empty but it's more of a change, a null delimiter . currently works for all the variants with * form and optional arguments and just fails for the default call. So if we test for {} instead need to change all the variants to accept that and it would still leave the form with . working in all variants except the default.

blefloch commented 2 years ago

@blefloch https://github.com/blefloch yes I wondered about using empty but it's more of a change, a null delimiter . currently works for all the variants with * form and optional arguments and just fails for the default call. So if we test for {} instead need to change all the variants to accept that and it would still leave the form with . working in all variants except the default.

Well, I'd say that making "." disappear in most cases is a (mild) bug. There is a standard notation (in 2d field theory) with delimiters being ":" on both sides, for instance ":a(x)b(y):", and someone could reasonably want a notation with "."

But I won't die on this hill.

davidcarlisle commented 2 years ago

@blefloch as you know the . wouldn't vanish in the other cases if it had a non zero delcode, the code to drop it in the default case could be more defensive and check that it has delcode 0 not just ifx equal to .

6601618158 commented 2 years ago

@davidcarlisle Thank you! Your code seems to do exactly what it's supposed to do! And I'm very sorry for the late reply.

blefloch commented 2 years ago

@davidcarlisle Ok, no strong opinion.

Enivex commented 8 months ago

You can I think do this. This does the test for . each time. It would be more efficient to just test at definition time and adjust the internal wrapper in that case not to use the delimiter but that would I think require more code re-arrangement.

\documentclass{article}
\usepackage{mathtools}

\MHInternalSyntaxOn
\renewcommand\MT_delim_default_inner_wrappers:n [1]{
   \@namedef{MT_delim_\MH_cs_to_str:N #1 _star_wrapper:nnn}##1##2##3{
      \mathopen{}\mathclose\bgroup ##1 ##2 \aftergroup\egroup ##3
    }
    \@namedef{MT_delim_\MH_cs_to_str:N #1 _nostarscaled_wrapper:nnn}##1##2##3{
      \mathopen{##1}##2\mathclose{##3}
    }
    \@namedef{MT_delim_\MH_cs_to_str:N #1 _nostarnonscaled_wrapper:nnn}##1##2##3{
      \ifx.##1\else\mathopen##1\fi##2\ifx.##3\else\mathclose##3\fi
    }
  }
\MHInternalSyntaxOff

\DeclarePairedDelimiterXPP{\restrict}[2]{}{.}{\rvert}{_{#2}}{#1}

\begin{document}

$\restrict{X}{Y}$

$\restrict*{X}{Y}$

$\restrict[\Big]{X}{Y}$

\end{document}

Are there any plans to have a more permanent solution to this? It works great, but I don't like putting stuff like this in every preamble

davidcarlisle commented 8 months ago

@daleif , anyone, thoughts on this, it looks relatively safe to me?

daleif commented 8 months ago

@davidcarlisle is that test save if someone for some reason messes with "." (aka active chars)?

davidcarlisle commented 8 months ago

@daleif well it's an ifx test so an active . would be safe but wouldn't match, could instead do an \if\noexpand. test so any . would match, depending what you want, although I think probably the ifx test is more natural as an active . typically wouldn't work for \left. either (depending what it's defined to).

blefloch commented 8 months ago

Typo \behin{macrocode}

Is omitting everything correct with respect to spacing, or should it do \mathopen{} and likewise \mathclose{} at the end? So probably

\mathopen\ifx.#1{}\else#1\fi

davidcarlisle commented 8 months ago

@blefloch you could argue either way (especially as left-right affects both start and end) but if you think of this as generating f(x)\bigr|_{...} then the natural thing (I think) is to have nothing at all at the start rather than an empty \mathopen

daleif commented 8 months ago

@blefloch @davidcarlisle Was wondering why it did not give an error. There is also a wrong \begin{macrocode}, will fix shortly

blefloch commented 8 months ago

Then maybe it makes sense to have two special cases: the empty argument omitting \mathopen{} and the . argument producing \mathopen{}?

This whole area of math spacing is confusing to me so I'll probably stop making noise here.

daleif commented 8 months ago

I prefer the current solution as it follows the "fence" rules for \left...\right

What are the use cases for \mathopen{} (it being empty), where is it needed?

blefloch commented 8 months ago

I don't think the new delimiter syntax follows \left....\right... in any sense. Skipping the \mathopen completely just leaves whatever spacing there would be without any atom there. As David says, this is what one expects visually, so probably the current implementation is ok. It nevertheless differs a lot from the \left.\right. case.

My understanding is that \left. \right. (with or without delimiter) creates a \mathinner, which can have additional thin mu spaces (only in textstyle and displaystyle) around it compared to \mathopen....\mathclose..., specifically when preceded by Ord, Op, Close, Inner, or followed by Ord, Open, Punct. See table around p205 of TeX by topic. So that's mostly the same spacing.

Ah, in fact, looking at the implementation mathtools for the starred form of a paired delimiter, it does the equivalent of \mathopen{}\mathclose{\left. STUFF \right.} (typically with left/right delimiters of course), so it actually matches the outside spacing of \bigl(STUFF\bigr) in all cases. The most logically consistent thing to do then is to always have \mathopen and always \mathclose.

Despite all of these abstract logical-consistency arguments concluding otherwise, I think the expected behaviour from a user point of view is actually the one currently implemented.

davidcarlisle commented 8 months ago

as @blefloch says the current version does nothing for . if the other delimiter is a fixed size \big etc. the . is only used in the variable branch using \left..\right Bruno's suggestion was to use {} to mean no spacing and {.} to mean \mathopen{} (but one could also ask if \nulldelimiterspace` should be used. I don't think multiple choices are that useful here, so long as we document the choice that is taken.

daleif commented 8 months ago

I think I did document what choice was made, that users should use . if they want an empty delimiter. As I feel that follows the syntax (not what goes on behind) of what to use with \left ... \right when a delimiter is suppose to be "empty". The question just is then whether to use

\ifx.##1\mathopen{}\else\mathopen##1\fi##2\ifx.##3\mathclose{}\else\mathclose##3\fi

I don't really see a scenario where it might make a difference?

blefloch commented 8 months ago

Compare $\sum_n\mathopen{}f_n(x)\mathclose{|}_{x=0}$ to $\sum_n f_n(x)\mathclose{|}_{x=0}$. The first has less space because Op followed by Open followed by Ord has no space, while Op followed by Ord directly has a thin space.

daleif commented 8 months ago

@blefloch that example probably makes me less inclined to add \mathopen{} as one has to explain to people why the spacing is smaller in this case. To me it also looks too close.

But it is an interesting example.

blefloch commented 8 months ago

Well, maybe it is actually an argument for omitting the \mathopen also in the starred version (which uses \mathopen{}\mathclose{\left...\right...} IIRC) when the first delimiter is .? In any case I think we cannot get to an always fully consistent spacing.

daleif commented 8 months ago

I think it is there incase OP + Close might do something strange. Similar to why it is \mathclose{\left ... \right} gets wrapped in.