pgf icon indicating copy to clipboard operation
pgf copied to clipboard

Choice key handler with deferred execution of code

Open fritzw opened this issue 3 years ago • 13 comments

A feature like .is choice, but the code of the choices is executed later, and not when assigning the value

The idea is from Juan on tex.sx, so I am going to quote his explanation here (CC BY-SA):

Using pgfkeys, I would like to have a choice key which, rather than immediately executing some code when a value is selected, stores the selection somewhere and then later on demand I can execute the code associated with said option.

An example should clarify things. I would like to define some sort of handler that allows me to write something like:

So that when I select an option

\pgfkeys{/action=jump} # remembers that I selected "jump"

the corresponding code \dojump is not executed just yet, but instead the selection is saved and executed only later until I actually say something like:

\pgfkeys{/action/.exec} # actually does the `\dojump`

This should allow the user to choose one option at some point, and change her mind later, and then put the option back. But nothing should be executed until I explicitly ask the code for the selected option to be executed.

Since I think that this is a very useful feature (I've missed it several times in the past) and I also think that my answer to that question (see compilable example below) is quite neat and simple, I'd like to propose including it directly into the pgfkeys package:

Usage example

\documentclass{article}
\usepackage{pgfkeys}

%%% Start of implementation
\makeatletter
\pgfkeysdef{/handlers/.deferred code}{% is called with \pgfkeyscurrentpath=/path/ChoiceKey/ChoiceValue
    \pgfkeysdef{/deferred choice/code\pgfkeyscurrentpath}{#1}%
    \edef\pgfkeyscurrentkey{\pgfkeyscurrentpath}\pgfkeys@split@path% now path=/path/ChoiceKey and name=ChoiceValue
    \pgfkeysedef{\pgfkeyscurrentkey}{\noexpand\pgfkeyssetvalue{/deferred choice/value\pgfkeyscurrentpath}{\pgfkeyscurrentname}}%
}
\makeatother
\pgfkeysdef{/handlers/.is deferred choice}{%
    \expandafter\pgfkeysedef{\pgfkeyscurrentpath/.execute}{% is called with \pgfkeyscurrentpath=/path/ChoiceKey
        \noexpand\pgfkeysgetvalue{/deferred choice/value\pgfkeyscurrentpath}{\noexpand\temp}%
        \noexpand\pgfkeysalso{/deferred choice/code\pgfkeyscurrentpath/\noexpand\temp={##1}}%
    }
    \pgfkeysalso{\pgfkeyscurrentpath/.is choice}%
    \pgfkeysalso{/deferred choice/code\pgfkeyscurrentpath/.is choice}%
}
%%% End of implementation

\begin{document}

  %%% Start of usage example
  \pgfkeys{
      /my package/.is family,
      /my package,
      align/.is deferred choice,
      align/left/.deferred code   = left:,
      align/right/.deferred code  = right:,
      format/.is deferred choice,
      format/bold/.deferred code    = bold:{#1},
      format/italics/.deferred code = italics:{#1},
  }
  \pgfkeys{/my package, align=right, format=bold}
  \pgfkeys{/my package, align=left, format=italics}
  %\pgfkeys{/my package, align=wrong} % Error: Choice 'wrong' unknown in choice key '/my package/align'
  % Inserts "left:italics:Hello" into the document
  \pgfkeys{/my package, align/.execute, format/.execute=Hello}
\end{document}

fritzw avatar Jan 18 '22 16:01 fritzw

I was not sure whether immediately opening a PR was the right way, so I opened this issue first to discuss it.

fritzw avatar Jan 18 '22 16:01 fritzw

What's the use-case for this within PGF? I would rather not add features to pgfkeys which is considered more or less frozen. If I could I would even remove the key filtering and put it into pgfplots because it's not used in PGF at all.

hmenke avatar Jan 18 '22 16:01 hmenke

@hmenke I guess that's a tension between pgfkeys as a part of pgf and as a generic tool.

josephwright avatar Jan 18 '22 16:01 josephwright

I think I (and perhaps @hmenke) could do with a more concrete example of a real-world problem this solves. (I'm thinking in terms of l3keys, but as it's inspired by pgfkeys, many similar ideas apply.)

josephwright avatar Jan 18 '22 16:01 josephwright

I'm right now thinking of adding a library mechanism to pgfkeys. That way I can accept all kinds of crazy features because they won't be loaded by default.

hmenke avatar Jan 18 '22 16:01 hmenke

Prototype is at https://github.com/pgf-tikz/pgf/pull/1129

hmenke avatar Jan 18 '22 18:01 hmenke

The reason this does not exist is because it is kinda "anti-pattern" for pgfkeys well eventually TeX itself being a terrible language at runtime. So up to the choice I think there is no problem. You can use a choice catalogue jump, sit, run. Each will set a particular value to a common macro \actiontodo and there is a central macro to do or exec that is when run, it checks the action to do from ... \actiontodo.

So when needed it is triggered but otherwise you can select the action as many times you need in the meantime. It will be deferred anyways because the trigger is not there. This is typically how TikZ stuff is done and how generally one hacks into TikZ.

ilayn avatar Jan 18 '22 20:01 ilayn

Reading the example back, I wonder why you don't just .store in then at point-of-use have a \@nameuse{...} containing the macro that is used for storage?

josephwright avatar Jan 18 '22 21:01 josephwright

Reading the example back, I wonder why you don't just .store in then at point-of-use have a \@nameuse{...} containing the macro that is used for storage?

That is another way to achieve the same effect (of course there are always multiple ways in programming), but I like three features of this approach:

  1. The included error checking of .is choice, which rejects unknown values.
  2. The implementations of the individual choices are separated from each other.
  3. (nearly the same as 2.) A central point-of-use macro would probably be littered with nested \if constructs or similar to distinguish the individual cases (or at least I know no better way). The .is choice acts like an implicit switch/case construct.

However, I can accept the argument that you don't want to add unnecessary features to pgfkeys. I mean, someone could always publish it as a separate package, but that is a lot more difficult to find. A library loading mechanism like in TikZ would probably make a lot of sense.

fritzw avatar Jan 18 '22 22:01 fritzw

A central point-of-use macro would probably be littered with nested \if constructs

That is pretty much the whole TikZ codebase internally.

ilayn avatar Jan 19 '22 05:01 ilayn

I thought about this a bit more yesterday and realised that this is in fact already possible without any extra code.

\documentclass{article}
\usepackage{pgfkeys}
\begin{document}
\pgfkeys{
  /my package/.is family,
  /my package,
  align/.is choice,
  align/left/.style={align/.execute/.code={left:}},
  align/right/.style={align/.execute/.code={right:}},
  format/.is choice,
  format/bold/.style={format/.execute/.code={bold:{##1}}},
  format/italics/.style={format/.execute/.code={italics:{##1}}},
}
\pgfkeys{/my package, align=right, format=bold}
\pgfkeys{/my package, align=left, format=italics}
\pgfkeys{/my package, align/.execute, format/.execute=Hello}
\end{document}

hmenke avatar Jan 19 '22 08:01 hmenke

Wow, that is a really neat solution (even though it took a pgf maintainer to come up with it). I still think that the explicit key names in my code make it a bit more clear what is going on (to the casual reader). But I guess that is no reason to add a new feature to pgfkeys. I'm fine if you want close this issue.

PS: Care to add your code as an answer on the TeX.SX question? ;-)

fritzw avatar Jan 19 '22 09:01 fritzw

align/.execute/.code={...} mixes up normal key and key handler hence I would suggest another key name, for example align/@execute.

muzimuzhi avatar Jan 20 '22 01:01 muzimuzhi

Closing this because it won't be implemented. The functionality can already be achieved in pgfkeys without adding extra code.

fritzw avatar Feb 16 '23 09:02 fritzw