4

When \myCoordinate accepts MANDATORY argument, following works:

\documentclass{standalone}
\usepackage{tikz}
\newcommand\myCoordinate[1]{#1}
\newcommand\myPath[1][(1,1)]{\path[draw=red]\myCoordinate{#1}--(1,0);}
\begin{document}
\begin{tikzpicture}
\myPath
\end{tikzpicture}
\end{document}

But not when \myCoordinate accepts OPTIONAL argument (replace \myCoordinate and \myPath definitions from above with the following code):

% modify \myCoordinate to accept optional argument and set default value for that argument to (0,0)
\newcommand\myCoordinate[1][(0,0)]{#1}
% replace curly braces (for mandatory argument) with square brackets (for optional argument)
\newcommand\myPath[1][(1,1)]{\path[draw=red]\myCoordinate[#1]--(1,0);}
% you can replace \myPath definition with following one so that optional argument of \myCoordinate reverts to default (but it still doesn't work)
%\newcommand\myPath[1][(1,1)]{\path[draw=red]\myCoordinate--(1,0);}

Judging by the error generated (Giving up on this path. Did you forget a semicolon?), tikz is complaining because something is wrong with expansion of the macro. And that expansion problem is caused by the optional argument of \myCoordinate.

I need \myCoordinate to work while accepting single argument, which is optional argument. I need to make this work with as less complicated code as possible.

On a sidenote, to learn how to send values only to specific optional arguments out of all optional arguments that a command accepts, see this.

Another approach fails alike.

When (instead of having \myCoordinate inside \myPath) \myCoordinate is passed to \myPath as an argument instead, it fails again when the argument of \myCoordinate is optional:

\documentclass{standalone}
\usepackage{tikz}
% following \myCoordinate accepts optional argument and fails when passed as an argument to \myPath
\newcommand\myCoordinate[1][0,0]{#1}
\newcommand\myPath[1][(1,1)]{\path[draw=red]{#1}--(1,0);}
\begin{document}
\begin{tikzpicture}
% neither of the two following lines work when \myCoordinate accepts optional argument
%\myPath[\myCoordinate] % fails
%\myPath[\myCoordinate[(0,0)]] % fails
\end{tikzpicture}
\end{document}

But when definition of \myCoordinate from above and the use of \myPath from above are changed to the following, it works again:

\newcommand\myCoordinate[1]{#1} % works
\myPath % works
%\myPath[\myCoordinate{(0,0)}] % works

So the optional-argument version of \myCoordinate can neither be used directly inside \myPath nor its value can be passed to \myPath as an argument.

My comment on the answer provided by @marmot.

There is considerable downside of passing (pgf) key-value pairs as arguments to a path's style compared to newcommand or NewDocumentCommand (at least in the form showed in your answer, not sure if it can be corrected):

  1. too long: compare \myLine[1][north] to \path[myLine={subtract=1,anchor=north}], it's like referring to 3 separate commands at once (\myLine, \subtract, \anchor, or \myLine[\subtract[1]][\anchor[north]])
  2. because of this, renaming (if need be) of subtract and/or anchor will require replacement of each subtract and/or anchor occurrence in each myLine style which has parameters passed to it (and there may be tens or hundreds, if not more, in the entire document): compare to renaming \subtract and/or \anchor newcommands which are used ONLY inside \myLine newcommand (in the document preamble)

If possible, solution should be (using value, not key-value, arguments---which relies on position or order of the values passed as arguments, not on their keys) the following: modify tikzset code to replace \path[myLine={subtract=1,anchor=north}] with something like \path[myLine={1,north}] where each of the arguments is optional (meaning that all of the following are valid and work as expected \path[myLine], \path[myLine={1}], and \path[myLine={north}]).

  • I'm not quite understanding. I see how \myL has one optional parameter, and can be called by \myL or \myL[south]. How does the other optional parameter fit in? How do you intend to call \myL? – Teepeemm May 12 at 4:29
  • \path[draw=red](\myN.#1 west)--(\myN.#1 east); makes no sense here because it doesn't use the second optional parameter #2 (default north) – user187802 May 12 at 4:40
  • 1
    I see in comments you've said you want to learn TeX macros from this question. There is a lot wrapped up in pgf, and I'm really not sure this is the best way to learn low-level stuff. Certainly I'd stick to pgf's own tools here. – Joseph Wright May 14 at 14:59
  • A couple of notes on the concepts here. First, positional arguments get very unwieldy after a while: there's a reason that lots of packages have moved to keyvals. Second, renaming is something that if it's required can be handled in the preamble by setting the old key name as an equivalent to the new one. On the other hand, changing names in stable documents should be a last resort: ideally the input syntax should be stable. (And a good editor should normally help a lot if a rename is required.) – Joseph Wright May 14 at 16:18
  • 1
    @bp2017 Covered in essence in tex.stackexchange.com/questions/8351/…: you have to change category codes before you tokenize. You'd need \ExplSyntaxOn\pgfkeys{/utils/exec = SOME_EXPL_CODE}\ExplSyntaxOff. – Joseph Wright May 14 at 17:32
8

The idea of pgf keys is precisely to avoid all this optional parameters and getting confused by them. Instead of rewriting all your commands and keeping in mind which parameter means what IMHO it is much simpler to use keys. In the example below, if you want to draw the path with all the default options, just do

\path[L];

If you want to subtract 1, have blue instead of red and south instead of north, do

\path[L={subtract=1,anchor=south,color=blue}];

You can reorder the keys as you want,

\draw[L={color=yellow,subtract=0,anchor=south}];

also works. Another advantage of this method is that it is downwards compatible, i.e. if you decide later that you need to add new keys the old commands will still work without the need of loading a new package.

Full example:

\documentclass[tikz,border=3.14mm]{standalone}
\newcounter{myC}
\tikzset{L/.style={/utils/exec=\tikzset{bp/.cd,#1},insert path={[draw=\pgfkeysvalueof{/tikz/bp/color}] 
(\the\numexpr\number\value{myC}-\pgfkeysvalueof{/tikz/bp/subtract}\relax.\pgfkeysvalueof{/tikz/bp/anchor}\space west)
--(\the\numexpr\number\value{myC}-\pgfkeysvalueof{/tikz/bp/subtract}\relax.\pgfkeysvalueof{/tikz/bp/anchor}\space east)}},
bp/.cd,
subtract/.initial=0,
anchor/.initial=north,
color/.initial=red}

\begin{document}
\begin{tikzpicture}[mystep/.code={\stepcounter{myC}}]
   \path node[draw,mystep](1)at (\number\value{myC},0){one};
   \path[L];
   \path node[draw,mystep](2)at (\number\value{myC},0){two};
   \path[L];
   \path[L={subtract=1,anchor=south,color=blue}];
   \draw[L={color=yellow,subtract=0,anchor=south}];
\end{tikzpicture}
\end{document}

enter image description here

The mystep code is copied from Andrew's answer.

ADDENDUM: Reply to the comment in the question on my answer. It is possible to make something like \path[myLine={north,1}]; work using key filters. However, in your case this would require more thoughts simply because a number is also a valid anchor. So how would TikZ know that 1 is not an anchor but the subtract key? (And personally I find it the suggestion not ver well thought, but of course there are well-thought versions of this, e.g. if you say \node[circle,red,... you do not have to say shape=circle,color=red because TikZ will figure this out for you. But in this case there is no ambiguity.)

  • 1
    Perhaps I should ask this as a "proper question", but is there any advantage to using L/.style={/utils/exec=...} over L/.code={\tikzset{bp/.cd,#1} \path...}? In this instance they seem to give the same result. Certainly, the latter is what I'd do "naturally". – Andrew May 12 at 7:28
  • 3
    @Andrew Yes, there are advantages. For instance, you can say ` \path[line width=1mm,L];, you can combine such paths \path[L,L={subtract=1,anchor=south,color=blue}];`, give them names for intersections and many more. – user121799 May 12 at 7:33
  • 1
    @bp2017 The pgf keys can be used outside of TikZ. And you can definitely build them into \newcommands, it is just that in this very context I feel that insert path is advantageous. See e.g. tex.stackexchange.com/a/481106/121799 for an example in which tons of pgf keys get used by a \newcommand. – user121799 May 12 at 15:15
  • 1
    @bp2017 Yes, sure. The problem may be though that, even though some things appear to be very basic, their implementation in LaTeX without packages is not, i.e. they may require tons of \expandafters and so on. This is why there are packages that simplify these things for us by providing us with helpers. These helpers include expl3, xparse and IMHO also pgf keys. If you want to know how all the low-level magic works, have a look at TeX by topic (texdoc texbytopic). – user121799 May 12 at 15:57
  • 2
    Just a note on the code: Inside of a \numexpr you don't need to put \the or \number prior to \value, so \the\numexpr\value{myC}-\pgfkeysvalueof{/tikz/bp/subtract}\relax does work. – Skillmon May 13 at 7:32
5

It is not clear to me what you are trying to do, but I don't think that you need two macros and that this following code might do want you want:

  \documentclass[tikz, border=5mm]{standalone}
  \usepackage{xparse}

  \newcounter{myC}
  \NewDocumentCommand{\myL}{O{north} O{0}}{%
    \def\myC{\the\numexpr(\themyC-#2)}
    \path[draw=red](\myC.#1 west)--(\myC.#1 east);
  }

  \begin{document}
     \begin{tikzpicture}[mystep/.code={\stepcounter{myC}}]
        \path node[draw,mystep](1)at (\themyC,0){one};
        \myL
        \path node[draw,mystep](2)at (\themyC,0){two};
        \myL
      \end{tikzpicture}
  \end{document}

Here is the output:

enter image description here

Note in particular the use of a .code statement to step the counter. I also added some coordinates for placing the nodes in the path statements since otherwise everything is drawn on top of each other.

  • I need \newcommand which you removed. I also use it for other means. – bp2017 May 12 at 6:17
  • @bp2017 Just use \newcommand\myN[1][0]{\the\numexpr(\value{myC}-#1)}. I think that if you try to use \myN inside your \myL command then you will have expansion issues, which is part of why I did this the way that I did. – Andrew May 12 at 6:32
  • That's the problem: I need to solve the expansion issue. – bp2017 May 12 at 15:05
  • Inside of \numexpr you shouldn't use \themyC but \value{myC}, \themyC might be redefined to output the value of myC as a Roman number or anything else. – Skillmon May 13 at 7:35
4

The way that pgf does parsing of it's arguments relies on an amount of 'look ahead' code, which takes the input and processes it one step at a time in order to extract possible actions. This is based on the idea that only a restricted range of material can appear in arbitrary positions inside a pgf/TikZ statement.

Here, looking for an optional argument involves an assignment (\let) which is not handled by the internals of pgf. At the TeX level, \let is not expandable so the two possible routes pgf has both fail: it's neither a known case nor something that can be expanded. The result would be an infinite loop, except pgf has a loop counter (set at 100 steps initially), which when exhausted leads to an alternative path giving the error.

As such, this is a fundamental limitation/design decision in how TikZ works. Other answers have tackled the problem in alternative ways, but you cannot use a macro with an optional argument here (at leas without trying to re-write significant parts of the TikZ parser).

Commands with only mandatory arguments do not have to have an assignment 'hidden' inside them, which is why they can work here. (Again, one would need to watch entirely arbitrary content.)

  • As far as I understand things you can install key filters in pgf and also use try such that TikZ could make sense out of \path[myLine={1,north}]. After all, this is how it makes sense of colors and so on. The problem though is that numbers are also valid anchors, so you cannot really come up with a sensible solution that also allows one to use numeric anchors at the same time. Other than that it is in principle possible to make something analogous to \path[myLine={1,north}] work if the OP provide a well-defined rule to discern them, which they don't. – user121799 May 14 at 21:54
  • @marmot Sure. I was looking at the core issue, and tried to find a way to make the 'TikZ expander' work here. But that is ultra-tricky: I think for an expl3 version we'd use a scan token approach and more expandable code .. – Joseph Wright May 14 at 21:56
  • I agree. My comment is just that TikZ does have means to "intelligently assign key values to keys", but of course this requires some effort. (My main concern is that I do not like evolving questions. The purpose of this site is IMHO not to serve one particular user, but many of them, so it is better to do things in steps IMHO.) – user121799 May 14 at 22:06
2

The problem is \myN. Using its code directly in \myL will work.

\documentclass{article}
\usepackage{tikz}
\newcounter{myC}

\makeatletter
\newcommand\myN[1][0]{\the\numexpr\themyC-#1\relax}
% square brackets now instead of curly braces
\def\myL{\@ifnextchar[\myL@i{\myL@i[0]}} 
\def\myL@i[#1]{\@ifnextchar[{\myL@ii[#1]}{\myL@ii[#1][north]}}
\def\myL@ii[#1][#2]{\path[draw=red](\the\numexpr\themyC-#1\relax.#2)--(1,0);}
\makeatother

\begin{document}
\begin{tikzpicture}
    \path node[draw,/utils/exec={\stepcounter{myC}}](1){one};
    \myL
    \path node[draw,/utils/exec={\stepcounter{myC}}](2){two};
    \myL[1][west]
\end{tikzpicture}

\end{document}

However, my code is only an example how it works. I have no idea what you really want to achieve ...

  • You have to be able to call myN from inside of myL. Which your answer does not do. Replace \the\numexpr\themyC-#1\relax with \myN[#1] (or anything that calls myN and passes an OPTIONAL argument to it). – bp2017 May 12 at 15:04
1

When \myCoordinate is used inside \myPath as in your initial code then there is no point in its having the capability to have an optional argument. There is no code path starting from \myPath which does not pass a parameter to the inner \myCoordinate. That is, there is no way to invoke \myPath such that the inner \myCoordinate falls back to its default argument.

Therefore, one option is to have an alternative command for the inner \myCoordinate which doesn't take an optional argument. The user-level \myCoordinate can also use this alternative so there isn't duplication of code.

This would result in code like the following:

\documentclass{standalone}
%\url{https://tex.stackexchange.com/q/490400/86}
\usepackage{tikz}
\newcommand\myCoordinate[1][(0,0)]{\myCoordinateAux{#1}}
\newcommand\myCoordinateAux[1]{#1}
\newcommand\myPath[1][(1,1)]{\path[draw=red]\myCoordinateAux{#1}--(1,0);}
\begin{document}
\begin{tikzpicture}
\myPath
\end{tikzpicture}
\end{document}

You could use the usual \makeatletter ... \makeatother code to make the inner command inaccessible to users.

  • ". . . there is no way to invoke \myPath such that the inner \myCoordinate falls back to its default argument." But there is a way to invoke \myCoordinate in such a way (such that it falls back to its default argument) elsewhere, instead of writing yet another \myCoordinate (\myCoordinateAux). That's the point: code reuse (not code duplication). – bp2017 May 14 at 19:23
  • @bp2017 as my code demonstrates, \myCoordinate is just a wrapper around \myCoordinateAux. So there is no code duplication. All \myCoordinate does is take its optional argument and pass it on to \myCoordinateAux. All the important code is in \myCoordinateAux. – Loop Space May 14 at 19:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.