Despairing over writing a function

I’m thinking of writing a novel. To be able to easily track who appears where, I was thinking of using a function per character. I want the function without argument to just return the name. An argument would either be an alternate name form, or empty for paragraphs where a human would know from context who’s speaking. Something like

#Liz said: "Oh, wow!" // normal case: just returns the name

#Liz[Elizabeth] said: "Oh, wow!" // returns the alternate name

#Liz[She] said: "Oh, wow!" // returns alternate text, but attributed to Liz for analysis

#Liz[] "Oh, wow!" // no name in final document,  but attributed to Liz for analysis

As far as I understand it, this argument can not have a default, since then it would have to be named. But, if I have a positional or variadic argument, it seems to default to the function name (value is Liz in a different font.) None of these seem to work

#let Liz(alt) = if alt == Liz { [Liz] } else { alt }
#let Liz(alt: [Liz]) = alt
#let Liz(..alt) = if alt.len() == 0 { [Liz] } else { alt.at(0) }

I’ve read the doc up and down, but it is fairly vague on details. How can I implement such a simple requirement?

There are a couple of issues with your definition of Liz:

  • Using a function without arguments doesn’t invoke it but inserts the function name.
  • alt is an arguments object; you may want to call .pos() to get at the positional arguments.
  • An empty positional argument is still an argument, so #Liz[] does have one argument, albeit an empty one.

Here is a modification of your example that kind of works:

#let Liz(..alt) = {
  let nomen = alt.pos().at(0)
  if nomen == [] {
    [Liz]
  }
  else {
    nomen
  }
}
#Liz[] comes.
#Liz[She] goes.

produces “Liz comes. She goes.”

As a minor note, with the approach you took, ..alt can be replaced with nomen since the argument is now always given again. The sample showing how to use arguments is still useful though!

Thanks for your detailed reply, dela!

  • That is weird! I get maybe needing that for debugging, but what good can that be in normal type setting? The simplest syntax should be the most useful one, i.e. call without arguments! However, I can live with that crutch, if I find how to format it like normal text (or bold while proof reading.)
  • My bad, I wrote that from memory. Check again, alt.at(0) is correct.
  • I expressed myself badly: In those cases I want the name in the source (for scripted analysis,) but not in the output.

This comes from how functions can be used (need to be usable) in a more programming-oriented context.

Consider this:

#let character = Liz

#character comes. #character[She] goes.

This wouldn’t work if Liz on its own was already [Liz], because the latter is not a function that you can call with [She] as a parameter.

Therefore, character is a function, not the content that you get when calling that function. To call a function without parameters you need to write e.g. character().

In your specific case, it would indeed be nice if Liz on its own called the function, but that would lead to problems in many situations where functions are used in a more general way. It may be possible to solve this somehow (e.g. symbols allow you to write both arrow and arrow.l), but functions are not great to use in this specific way.

Indeed, I would have also missed that arguments supports this.

In this case, I think the closest you can get is this:

#let Liz(..alt) = {
  if alt.len() == 0 [Liz]
  else { alt.at(0) }
}

#Liz() said: "Oh, wow!" // normal case: just returns the name

#Liz[Elizabeth] said: "Oh, wow!" // returns the alternate name

#Liz[She] said: "Oh, wow!" // returns alternate text, but attributed to Liz for analysis

#Liz[] "Oh, wow!" // no name in final document,  but attributed to Liz for analysis

Liz is replaced by Liz(), otherwise it looks the way you wanted. As explained above, a plain Liz is probably not possible, unless I am missing a hacky way.

3 Likes