I’ve noticed that macros have been deprecated. I know they won’t be going away any time soon, but I thought - as a learning exercise - I’d convert a macro of mine to a procedure. Sounds simple, but I’ve spent probably hours now unsuccessfully trying to get it to work as smoothly as the macro.
My macro has several parameters, and depending on what I’m doing I’ll either pass in a string to be output as-is, or I need to set a variable to the result of a filter and pass that in. For my use case at least, procedures appear to be a retrograde step.
I have created a simplified test case below:
\define dtest(arg) $arg$
\procedure ptest1(arg) <<arg>>
\procedure ptest2() <<arg>>
* Macro:
** <<dtest pass>>
** <$let test={{{[[]addsuffix[ss]addprefix[pa]]}}}><<dtest $(test)$>></$let>
* Procedure1 (with parameter, called same as macro)
** <<ptest1 pass>>
** <$let test={{{[[]addsuffix[ss]addprefix[pa]]}}}><<ptest1 $(test)$>></$let>
* Procedure1 (with parameter, variable named as parameter)
** <$let arg=pass><<ptest1>></$let>
** <$let arg={{{ [[]addsuffix[ss]addprefix[pa]] }}}><<ptest1>></$let>
* Procedure2 (no parameter, called same as macro)
** <<ptest2 pass>>
** <$let arg={{{[[]addsuffix[ss]addprefix[pa]]}}}><<ptest2>></$let>
* Procedure2 (no parameter, calls adjusted to work)
** <$let arg=pass><<ptest2>></$let>
** <$let arg={{{[[]addsuffix[ss]addprefix[pa]]}}}><<ptest2>></$let>
Which renders as:
Macro:
pass
pass
Procedure1 (with parameter, called same as macro)
pass
$(test)$
Procedure1 (with parameter, variable named as parameter)
Procedure2 (no parameter, called same as macro)
pass
Procedure2 (no parameter, calls adjusted to work)
pass
pass
The $let syntax isn’t too bad, but that implies to me that parameters to procedures are of no use to me - I’m better off just setting variables as needed prior to using the procedure.
After all that, the end result appears to be extra typing for no benefit, meaning I’m better off forgetting procedures and sticking with macros.
Am I doing procedures wrong? Is there something I’ve missed?
Addendum: I was just about to hit “Post”, when I made the mistake of trying out the last example of $let+procedure with my actual macro and absolutely everything broke! Something is up with quotes inside the procedure. I’ll have to spend another hour or so experimenting and creating a new example. I am finding “\procedure” completely frustrating, while “\define” “just works”.
This is certainly an option. But IMO, if you need a non-literal parameter value for a procedure, it’s easiest to set it inline with the $transclude widget. For instance, rather than the following, which fails because $(test)$ isn’t substituted in this context…
This is obviously longer than a short-form macrocall like <<ptest1>> — but on the other hand, it’s slightly shorter than the $let + macrocall combination. So if you have multiple procedures (or macros, or functions) all using the same variable with the same values, it’s probably more efficient to set the variables with $let (scoping all of them at once) and omit all the relevant parameters from the procedure definition. And conversely, for procedures that use unique variable values, I’d switch to $transclude and set the parameter as a widget attribute.
Alternatively, if you want to specify a default value for a given parameter in a procedure, you can omit it from the named parameter list and instead define it with $parameters, scoped to the procedure definition:
There are a few differences between macros and procedures. At first it seems if there is simply less ways to access the parameters and this is true on the surface. Where once you could use $name$ and $(name)$ now you can use only a variable reference <<name>>.
However in addition to this simplification there are other changes that support procedures and enable far more than we had with define. In no particular order;
Because the parameters are available as variables they can be used directly in filters with <name>
They can be used as values to other parameters param=<<name>> and directly in wikitext <<name>> without further delimiting param="$name$" as we had to.
Whilst as always we have access to variables defined outside our procedure, if that name is used as a parameter, it replaces the variable outside (gives it a local copy of the variable)
procedures can now be called using the <$transclude $variable=procname/> which enables even more to when invoking procedures.
Though note you can still use macrocall to call a procedure, it just does not support some new features documented in the $parameters widget
Something we often used the old macros for was Substitution, this becomes available using the backticks on values to attributes parameters
param=`literal ${ filter }$ and $(variable)$ $(parameter)$`
Beware that the above ${ filter }$ only returns the first value so you may need to join[] or join[ ] it.
But we have also being given the substitute Operator which allows this as well within a filter;
$replacable$ $(varname)$ literal ${ filter }$
and this can use used in functions below
All of the above is before we make use of the $parameters widget
Although documented for specific reasons the $parameters widget can be used in a number of ways we could describe as off script. I am yet to fully explore that
one feature is the $params=varname which is capable of returning all parameter/values
And this allows us to also leverage the $genesis widget to generate html tags or override existing widgets.
Further enhancements that have revolutionised tiddlywiki, in ways where once only macros existed, includes
Functions which are a great way to use filters in our procedures (or macros for that matter)
including custom operators
Unless you intervienn functions return the first value when referenced like a variable <<function>> but returns plain text to use without the $text widget.
Functions are evaluated in line and not at the end, allowing us to avoid the $wikify widget to get intermediate results.
Custom widgets, a great option if you want to pass blocks of text as a parameter
also provides an alternative to both \define and \procedure
Using parameters or the new ability to pass parameters during transclusion
also provides an alternative to both \define and \procedure
If I look at your examples most are calling a macro/procedure with a variable.
\function auto() [[]addsuffix[ss]addprefix[pa]]
<$macrocall $name=dtest arg=`${ [[]addsuffix[ss]addprefix[pa]] }$`/>
<$transclude $variable=dtest arg=`${ [[]addsuffix[ss]addprefix[pa]] }$`/>
<$transclude $variable=dtest arg=<<auto>> />
In this case however `<<auto>>` gives the same result
These should work
It seems odd to me you are using [[] at the beginning of filters, as it returns an empty title but interestingly is[blank] not missing.
the benefit is a more consistent formatting of parameters, plus they have to be named, which ultimately improves readability of the code.
Back on topic, what I’ve found is that once HTML becomes part of a \procedure variable substitution is no longer possible:
\define dtest(arg) <span style="text-decoration:$arg$">text</span>
\procedure ptest1(arg) <span style="text-decoration:<<arg>>">text</span>
\procedure ptest2() <span style="text-decoration:<<arg>>">text</span>
* Macro:
** <<dtest underline>>
** <$let test={{{[[]addsuffix[line]addprefix[under]]}}}><<dtest $(test)$>></$let>
* Procedure1 (with parameter, called same as macro)
** <<ptest1 underline>>
** <$let test={{{[[]addsuffix[line]addprefix[under]]}}}><<ptest1 $(test)$>></$let>
* Procedure1 (with parameter, variable named as parameter)
** <$let arg=underline><<ptest1>></$let>
** <$let arg={{{ [[]addsuffix[line]addprefix[under]] }}}><<ptest1>></$let>
* Procedure2 (no parameter, called same as macro)
** <<ptest2 underline>>
** <$let arg={{{[[]addsuffix[line]addprefix[under]]}}}><<ptest2>></$let>
* Procedure2 (no parameter, calls adjusted to work)
** <$let arg=underline><<ptest2>></$let>
** <$let arg={{{[[]addsuffix[line]addprefix[under]]}}}><<ptest2>></$let>
only the first lot of text produced using the \define is underlined, as opposed to none of the \procedure output. The literal <<arg>> is output in the HTML without being substituted.
On re-reading the post of @TW_Jones, I’ve noted mention of a $genesis widget. I’ll see what that does…
Mostly, I’m just wanting to learn how procedures work. So far, my conclusion is “not well” or at least “they’re very much more complicated than macros”. I’ve spent all day so far (it’s now late afternoon) trying to get a procedure to do what I want without success. From memory, my macro was working in pretty much the time it took to type it…
With the help of a stylesheet it makes badges like https://shields.io/ . Originally it used parameters, but I now like the variables created by $let so much I dropped the parameters. I’m interested in what the equivalent procedure would be. Examples:
I will have to look at it tomorrow. but it’s important for you to know procedures are if anything similar and in your above examples it is dependant on the parameter syntax of define. it is simply a matter of using a different way to substitute in the parameter values.
I will see if I can look later tonight otherwise tomorrow.
Thank you very much for your help. I was staring at your code for a while wondering what you’d changed before I noticed the backticks! The variables outside the HTML didn’t need changing (they were being output as-is instead of being substituted). Now there’s a mystery margin around the dt/dd.
… solved! I’d removed \whitespace trim from my example, but it’s clearly important!
To add a little more to this thread, I had dropped parameters from my procedure because they were overriding identically named variables, preventing me from using the $let to set values. The solution turned out to be obvious: use $parameters and have the parameters default to the corresponding variable:
If necessary, default fallbacks are still available using filters - see “color” and “labelcolor” below. Filters could also be used without a fallback, see the “status” parameter.