Why does `compare` work with a hard-coded value, but not with a calculated value?

Hi there. I’m working on a pluralize macro, which should output the correct form of the given English-language noun based on the value of the count variable.

  • If its value is 1, it should output the singular form; otherwise, it should output the plural form.
  • Eg. <<pluralize 9 spoon spoons>> should give 9 spoons, while <<pluralize 1 boat boats>> should give 1 boat.

It seems to work when the value for count is a plain or hard-coded value, but not when it’s calculated.

I’d really appreciate an explanation of why this is happening, much more than a practical solution. I feel like I’m missing a fundamental of how macros, variables and rendering work. Please help :slight_smile:

A. Macros & View

\define age-from-birthday()
<$let now={{{[<now TIMESTAMP>]}}} birthday={{{ [{!!birthday}format:date[TIMESTAMP]] }}} age={{{[<now>subtract<birthday>divide[31536000000]floor[]]}}}><<age>></$let>
\end

\define pluralize(count, singular, plural)
<!--//DEBUG: I got count='<<__count__>>', singular=<<__singular__>>, plural=<<__plural__>>. Is it 1? {{{ [<__count__>match[1]then[yes]else[no]] }}}//-->
<% if [<__count__>compare:number:eq[1]] %>
  <<__singular__>>
<% else %>
  <<__plural__>>
<% endif %>
\end

\define years-old-string()
<<age>> <<pluralize "$(age)$" "year" "years">> old
\end

!! Age as a plain value

<ul>
<$list filter=[[Charlie]]>
<$let age=1>
<li>{{!!title}} is {{!!age}} <<years-old-string>></li>
</$let>
</$list>
</ul>

!! Age as a calculated value

<ul>
<$list filter=[[Alice]][[Bob]]>
<$let age={{{ [<age-from-birthday>] }}}>
<li>{{!!title}} is {{!!age}} <<years-old-string>></li>
</$let>
</$list>
</ul>

B. Data

  • Tiddler Alice with field birthday = 20241212120000000 – one year old
  • Tiddler Bob with field birthday = 20000303120000000 – 25 years old
  • Tiddler Charlie need not exist; I am only using the name, and a hard-coded age of 1.
  • Today is a date in December, 2025.

C. Output

Age as a plain value

  • Charlie is 1 year old [this is correct]

Age as a calculated value

  • Alice is 1 years old [this is incorrect]
  • Bob is 25 years old

(Edited: used a better algorithm for calculating age)

Macros just return their content. It is then up to the calling context to process that content to render the output.

When you write:

<$let age={{{ [<age-from-birthday>] }}}>
<li>{{!!title}} is {{!!age}} <<years-old-string>></li>
</$let>

The contents of the age variable is literally

<$let now={{{[<now TIMESTAMP>]}}} birthday={{{ [{!!birthday}format:date[TIMESTAMP]] }}} age={{{[<now>subtract<birthday>divide[31536000000]floor[]]}}}><<age>></$let>

and it is only when you use <<age>> to produce output here:

\define years-old-string()
<<age>> <<pluralize "$(age)$" "year" "years">> old
\end

that it is actually evaluated to show the computed value

To get the evaluated macro output as a variable, use the $wikify widget instead of the $let widget, like this:

<$wikify name=age text=<<age-from-birthday>>>
<li>{{!!title}} is {{!!age}} <<years-old-string>></li>
</$wikify>

Also note that when you are invoking a macro to get its output (without applying any further filter syntax), you don’t need to use triple curly braces (filtered transclusion). Thus, you can write:

<$let now=<<now TIMESTAMP>> ...

and

<$wikify name=age text=<<age-from-birthday>>>

-e

Thanks so much for the clear and helpful explanation, @EricShulman!

@copper99 I noted above your code snipit is using the \define which is deprecated and using the %if which is quite new. Without complicating the answer I just want to suggest you have a look at using some of the more modern methods. Make the define a procedure but even more consider using functions which are filters but they are evaluated (before the final render) so don’t have the problems you found above.

  • You may also just want to add the suffix s to pluralise and only handle exceptions to this rule.