5

I'm having some trouble fixing a macro I wrote. I believe that it has to do with \let, \def, and \edef. I have gone through:

What is the difference between \let and \edef?

but I still can't seem to sort out what I'm misunderstanding. The code is a bit weird, but it is the simplest example I could come up with that resembles my actual code.

What I would like:

  1. Teach me what I'm doing wrong and how to fix it. In particular, my (obviously wrong) understanding of \let led me to believe that this should have worked. I tried playing with \def and \edef without success.
  2. If the example is clear enough to indicate that I am going about this completely wrong, please suggest a better way of writing such a macro. (Though I'm a bit concerned that since I've simplified this so much, the suggested rewrites won't be flexible enough or will miss my actual use case.)

Code:

\documentclass{minimal}
\usepackage{etoolbox}
\makeatletter

% in actual code these have multiple required arguments
\def\myobjectXA#1{XA:#1}
\def\myobjectYA#1{YA:#1}
\def\myobjectXB#1{XB:#1}
\def\myobjectYB#1{YB:#1}

\newcommand{\makefunc}[3]{%
    \ifstrequal{#1}{A}
        {%
            \let\myobjectX\myobjectXA
            \let\myobjectY\myobjectYA
        }%
        {%
            \let\myobjectX\myobjectXB
            \let\myobjectY\myobjectYB
        }
    \expandafter\def\csname #2\endcsname##1{%
    \ifstrequal{##1}{X}%
      {\def\mytemp{\myobjectX{#3}}}
      {\def\mytemp{\myobjectY{#3}}}
    % In actual code \mytemp is is sent to another macro, which then
    % adds additional required arguments. This is why I think using
    % \let is not an option. Using \edef also didn't work for me.
    \mytemp
    }
}
\makeatother

\begin{document}
\setlength{\parindent}{0pt}
\makefunc{A}{hello}{1}
\makefunc{B}{goodbye}{2}
\hello{X}\\   % desired:  XA:1     actual:  XB:1
\hello{Y}\\   % desired:  YA:1     actual:  YB:1
\goodbye{X}\\ % XB:2
\goodbye{Y}   % YB:2
\end{document}

Here is some additional information in response to the comments:

Recent engines are all I care about in terms of compatibility.

What's motivating this example is that I have two families X and Y that represent whether you want harpoons or arrows on top of a symbol. Within the families, there are 3 options A,B,C that specify whether it is left pointing, right pointing, or leftright pointing. So I'm making a macro that will let people customize the base object and the family of decorators. I'm expecting people will write it like so:

\makemacro{arrow}{Qaz}{Q}
\makemacro{harpoon}{Qwe}{W}
\Qwe(>)       % W with right-pointing harpoon
\Qaz(<)       % Q with left-pointing arrow
\Qaz(<)[0][5] % Q with left-pointing arrow and subscript 0:5
\Qaz(<)[][5]  % Q with left-pointing arrow and subscript  :5
\Qaz(<)[0][]  % Q with left-pointing arrow and subscript 0:
\Qaz(<)[0]    % Q with left-pointing arrow and subscript 0

So it's not strictly necessary that I use a string comparison, but I figured the readability was nice. Probably I could use xkeyval or something similar instead. The two \ifstrequal checks in the demo code were quick choices for selecting the family (arrow or harpoon) at macro definition time, and for selecting the pointing direction at "runtime". Anyway, hopefully that provides enough information to figure out how I could use \edef properly.

  • It's tough to pick a single correct answer. The most recent ones answered my deeper question, but this wasn't strictly asked for until I clarified. What is the typical procedure for this? @Werner's answer most directly answered my original text. – Tom Oct 18 '14 at 1:20
5

You should see your mistake when you compile the following document body:

\makefunc{A}{hello}{1}
\hello{X}\\   % XA:1
\hello{Y}\\   % YA:1
\makefunc{B}{goodbye}{2}
\goodbye{X}\\ % XB:2
\goodbye{Y}   % YB:2

The output is as you want it. The reason for that is because when you create your function \hello (via \makefunc{A}), the internal definition requests the use of \myobjectX and \myobjectY. These are \let to some other magic definition. But, with a subsequent call to \makefunc{B}, \myobjectX and \myobjectY are now set to something completely different (overwritten); something \hello has no influence over. The above delay of defining \goodbye and using \hello before that definition is what produces the correct output.

You could make a macro-specific \myobject, like I've done below. I used a \csname ...\endcsname construction:

\documentclass{article}
\usepackage{etoolbox}

% in actual code these have multiple required arguments
\def\myobjectXA#1{XA:#1}
\def\myobjectYA#1{YA:#1}
\def\myobjectXB#1{XB:#1}
\def\myobjectYB#1{YB:#1}

\newcommand{\makefunc}[3]{%
  \ifstrequal{#1}{A}
    {%
      \expandafter\let\csname myobjectX#2\endcsname\myobjectXA
      \expandafter\let\csname myobjectY#2\endcsname\myobjectYA
    }%
    {%
      \expandafter\let\csname myobjectX#2\endcsname\myobjectXB
      \expandafter\let\csname myobjectY#2\endcsname\myobjectYB
    }%
  \expandafter\def\csname #2\endcsname##1{%
  \ifstrequal{##1}{X}%
    {\def\mytemp{\csname myobjectX#2\endcsname{#3}}}
    {\def\mytemp{\csname myobjectY#2\endcsname{#3}}}
  \mytemp%
  }%
}

\begin{document}
\setlength{\parindent}{0pt}
\makefunc{A}{hello}{1}
\makefunc{B}{goodbye}{2}
\hello{X}\\   % XA:1
\hello{Y}\\   % YA:1
\goodbye{X}\\ % XB:2
\goodbye{Y}   % YB:2
\end{document}
  • Thanks, I had tested out the permutation of lines a few times. So is there no way to "bind" the value of \myobjectX during the definition of \mytemp? As the comment shows, I had thought \edef should have fixed that for me. – Tom Oct 17 '14 at 6:07
  • @Tom \edef will indeed do the job if you use suitably expandable code inside it. That does not apply to \ifstrequal, which is not expandable. Can you give more details on the nature of the first argument to \makefunc: is it a single token, doe you really need a string comparison, etc. Also, what engines have to be supported (i.e. can we assume only recent ones or does Knuth's TeX need to be covered). – Joseph Wright Oct 17 '14 at 6:14
  • Recent engines only. I've updated the post with more information about my intended use case. – Tom Oct 17 '14 at 6:56
  • I'm selecting this answer b/c it most directly demonstrated what was wrong with my code and revealed how to deal with that particular issue. The other answers are fantastic as well and show alternative approaches to achieve my overall goals. – Tom Oct 19 '14 at 18:50
4

If all that is needed is to obtain the appropriate macro name, there is no need for any tests at all, as things can be done using \csname

\newcommand\makefunc[3]{%
  \expandafter\def\csname #2\endcsname##1{%
    \csname myobject##1#1\endcsname{#3}%
  }%
}

This will work even with more complex names as you can simply use a \cnsame definition for you various different outcome macros.

On the other hand, if you don't want a strict equivalence between the input text and which internal macro it picks then you do want a test. Perhaps the easiest way with a modern engine is to use \pdfstrcmp or equivalent. This is low-level syntax, but works nicely. We could either do it without an \edef (comparison at each use)

\usepackage{pdftexcmds}
\makeatletter
\newcommand\makefunc[3]{%
  \expandafter\def\csname #2\endcsname##1{%
    \csname
      myobject%
      \ifnum\pdf@strcmp{\unexpanded{##1}}{X}=\z@ X\else Y\fi
      \ifnum\pdf@strcmp{\unexpanded{#1}}{A}=\z@ A\else B\fi
      \endcsname{#3}%
  }%
}

or do the first comparison 'up front' using \edef

\usepackage{pdftexcmds}
\makeatletter
\newcommand\makefunc[3]{%
  \expandafter\edef\csname #2\endcsname##1{%
    \noexpand\csname
      myobject%
      \unexpanded{\ifnum\pdf@strcmp{\unexpanded{##1}}{X}=\z@ X\else Y\fi}%
      \ifnum\pdf@strcmp{\unexpanded{#1}}{A}=\z@ A\else B\fi
      \noexpand\endcsname{#3}%
  }%
}
\makeatother

To see the difference, do \show\hello for the two cases.

2

If I understand what do you need, you plan to write a macro \makefunc which does following:

\makefunc \hello   A1
\makefunc \goodbye B2

\hello X    % -> XA:1
\hello Y    % -> YA:1
\goodbye X  % -> XB:2
\goodbye Y  % -> YB:2

This could be done by only one line of definition:

\def\makefunc#1#2#3{\def#1##1{##1#2:#3}}

Note that there is no \let, no \edef, no \pdfstrcmp etc. Simple tasks could be done by simple tools.

Now to your second part of your problem about \Qwe and \Qaz macros.

\def\makemacro#1#2#3{%
  \def#1(##1){{\makemacroK{#3}^{\csname\ifx##1<left\else right\fi#2\endcsname}}\makemacroA}%
}
\def\makemacroA{\def\makemacroI{}\futurelet\next\makemacroB}
\def\makemacroB{\ifx\next[\expandafter\makemacroC\fi}
\def\makemacroC[#1]{\def\makemacroI{#1}\futurelet\next\makemacroD}
\def\makemacroD{\ifx\next[\expandafter\makemacroE\else_{\makemacroI}\fi}
\def\makemacroE[#1]{_{\makemacroI:#1}}
\def\makemacroK#1{\mathop{%
  \setbox0=\hbox{$#1_0$}\setbox2=\hbox{$#1\null_0$}%
  #1\kern\wd0\kern-\wd2}\limits
}

\makemacro \Qaz {arrow}{Q}
\makemacro \Qwe {harpoonup}{W}

$      \Qwe(>),      % W with right-pointing harpoon
 \quad \Qaz(<),      % Q with left-pointing arrow
 \quad \Qaz(<)[0][5] % Q with left-pointing arrow and subscript 0:5
 \quad \Qaz(<)[][5]  % Q with left-pointing arrow and subscript  :5
 \quad \Qaz(>)[0][]  % Q with right-pointing arrow and subscript 0:
 \quad \Qaz(<)[0]    % Q with left-pointing arrow and subscript 0
$

harpoons

Note that the basic macro problem is solved on one line too. Much more work were done because of placing arrow above nucleus, calculating kerning between nucleus and subscript (\makemacroK) and scanning the optional arguments in square backets (\makemacroA,B,...,E). The scheme of the math typpesetting is:

\mathord{\mathop{nucleus kerning correction}\limits^{arrow}}_{subscript}
  • Wow that is so much cleaner than what I have. Is it possible to modify this so that you can add a fixed superscript for each macro? In your solution, it seems like you use up the superscript in order to place the arrow. I use this sometimes when I don't need an arrow for a symbol, but want each use of the symbol to have the same superscript: \Qsc[0][3] --> W^{+}_{0:3}. – Tom Oct 17 '14 at 18:55
  • @Tom It seems that nobody uderstand this simpicity because here is zero votes. You can try to define \def\makemacrox#1#2#3{\def#1{{#2}^{#3}\makemacroA} and to declare \makemacrox \Qsc W+ and to use \Qsc[0][3]. – wipet Oct 18 '14 at 6:14
  • it looks like that modification would prevent arrows. Sorry for not being clear...and in fact, making a factually incorrect statement on my previous comment. Correction: sometimes, I need a superscript in addition to the arrow. Since your original solution uses the superscript to provide the arrow, it seems like it might be harder to modify it so that there both a regular superscript AND an arrow. – Tom Oct 19 '14 at 1:00
  • @Tom You can write \Qaz[0][5]^+ or you can add a new parameter #4 to the \makemacro and use it in ...\endcsname}}^{#4}\makemacroA}. – wipet Oct 19 '14 at 16:01

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.