Importvariables widget magic

With the help of this nice site, I have discovered </$importvariables> today. It was exactly what I was looking for!

Here I have to say, that the official doc about the \import pragma would be better indicating the existence of the </\importvariables> widgets which can handle dynamic import, that means import not known at the coding point but at thhe run point.

I was trying to have a kind of Visitor design patterns for an application for managing games tournaments, where in particular the way to manage points are rules and scoring system dependent. So I was obliged to import all the stuff manually before deciding what to call. With this widget, my code is clean and I have far more less things to import!

To be clearer, here is some of my code below.

First, I have created a generic macro which is acting a bit like if I had class template or an interface in tiddlywiki (the idea is not to write javascript when you can do it in plai tiddlywiki).

== code for $:/user/tournix/macros/calcScores (tagged $:/tags/Macro) ==

\define calcScores(result)
<<callForgedMethod targetMethod:calcScores extraName:result extraValue:"""$result$""">>
\end

== code for $:/user/tournix/macros/forgeMethod (not tagged!)==

\define forgeMethod() $(rule)$_$(scoring)$_$(targetMethod)$

The idea is to build 3 variables to build the real names of the function to call according to my naming scheme. This is handled in the next macro.

== code in $:/user/tournix/macros/callForgedMethod (tagged $:/tags/Macro) ==

\define callForgedMethod(targetMethod round extraName:extra extraValue)
\import $:/user/tournix/macros/forgeMethod
<$let
  targetMethod = """$targetMethod$"""
  data = "$:/user/data"
  tournmt = {{{ [<data>get[tournament]] }}}
  tourn = {{{ [<data>addsuffix[/tourn/]addsuffix<tournmt>] }}}
  rule = {{{ [<tourn>getindex[rule]!is[blank]else[hott]] }}}
  scoring = {{{ [<tourn>getindex[scoring]!is[blank]else[24zero]] }}}
  imported = {{{ [[$:/user/]addsuffix<rule>addsuffix[/macros/scoring/]addsuffix<scoring>addsuffix[/]] }}}
>
<$action-log $message="callForgedMethod(targetMethod~$targetMethod$, round~$round$, extraName~$extraName$, extraValue~$extraValue$)" $filter="forgeMethod imported"/>
<$importvariables filter="[prefix<imported>]">
<$macrocall $name=<<forgeMethod>> round="""$round$""" $extraName$="""$extraValue$"""/>
</$\importvariables>
</$let>
\end

This is a little bit more convoluted that I wished, but it does the job and is somehow not too messy.

As a side note, I had to declare forgeMethod in a tiddler of its own because:

  1. if it were declared within callForgedMethod it would not been able to handle the $(variable)$ notation for the variables created by forgeMethod
  2. it allows me to not tag it with $:/tags/Macro and to hide it from outside world where it could be used in an improper way.

The great benefit of the <$importvariables> widget is that I can now simplify my naming. At the beginning, I was importing all the function before calling one. So all the functions had to have a different name, hence the long names. Now, they can have a simple name. Not calcScores in my example, because this the name of the “generic” method and it shall be unique. But it could be as simple as _calcScores for each such function. That would also lead to a simpler forgeMethod macro:

\define forgeMethod() _$(targetMethod)$

Any comment and ideas about making it even simpler would be greatly appreciated!

I didn’t test your code. … But it doesn’t look too complicated. So if it works and is maintainable for you, you should probably stick with. …

Without digging much deeper into your code and without the “greater picture” it’s very hard for us to make informed suggestions. …

Once the pending PR No. 6666 at GH is merged there may be possibilities to make your code simpler. So it will probably be TW v5.2.4 …

It will introduce several new concepts to the TW core, which can optionally be used by authors, that want to “explore the rabbit hole”.

The changes in this PR provide powerful new ways to achieve common tasks, and unlock completely new capabilities that were previously impossible in wikitext.

  • Procedures, which are essentially what macros should have been; they work in exactly the same way except that parameters are exposed as simple variables (without the double underscores) and no textual substitution takes place
  • Custom widgets, allowing the creation of widgets in wikitext, and the redefinition of built-in widgets
  • Functions, a new way to encapsulate filter expressions with named parameters
  • Custom Filter Operators, allowing functions to be used as custom filter operators
  • Parameterised transclusions, allowing strings and wikitext trees to be passed to transclusions
  • Global definitions that do not clutter up the variable namespace, and do not have to be imported before use
1 Like

@pmario Hey, this looks very nice indeed!

As for my code, I’ve just made it simpler. Here’s how:

  1. I realised that hor each macro called, I had to import a lot of them with a prefix filter operator, whith means it would have been very ineffeicient
  2. so I elicted to import only the function to be called! which I should have done from the very start, but that’s what refactorisation is for…
  3. and then I realised that the forgeMethod is not needed and got rid of it.

So now my core system is only one function, callForgedMethod, as below:

\define callForgedMethod(targetMethod round extraName:extra extraValue)
\import $:/user/tournix/macros/forgeMethod
<$let
  data = "$:/user/data"
  tournmt = {{{ [<data>get[tournament]] }}}
  tourn = {{{ [<data>addsuffix[/tourn/]addsuffix<tournmt>] }}}
  rule = {{{ [<tourn>getindex[rule]!is[blank]else[hott]] }}}
  scoring = {{{ [<tourn>getindex[scoring]!is[blank]else[24zero]] }}}
  imported = {{{ [[$:/user]] [<rule>]  [<scoring>] [[macros/_$targetMethod$]] +[join[/]] }}}
>
<$action-log $message="callForgedMethod(targetMethod~$targetMethod$, round~$round$, extraName~$extraName$, extraValue~$extraValue$)" $filter="forgeMethod imported"/>
<$importvariables filter=<<imported>>>
<$macrocall $name="""_$targetMethod$""" round="""$round$""" $extraName$="""$extraValue$"""/>
</$\importvariables>
</$let>
\end

(I have seen the need for an other parameter of variable name – as optional as the first one).

And here’s the use of it. First, b target method and then the generic method that calls the target method by using some conventional variable that MUST be set before calling it. That’s simple and effective.

code of $:/user/dba/420sodomos/macros/_calcScores

\define _calcScores(result)
<$action-log $message="dba 420sodomos _calcScores($result$)"/>
<$let regex="^([^<>= ]+)( |>>?|<<?|==?)([^<>= ]+)$">
<$set name=scores select=0 filter="[[$result$]dump[from result]search-replace::regexp<regex>,[$2]dump[through outcome]search-replace[==],[2 2]search-replace[=],[1 1]search-replace[<<],[2 3]search-replace[<],[0 4]search-replace[>>],[3 2]search-replace[>],[4 0]dump[to points]]">
<$action-log $messages="dba 420sodomos _calcScores done" $filter=scores/>
<<scores>>
</$set>
</$let>
\end

use:

<$let thisround = {{{ [all[current]get[round]] }}}>
<$button actions=<<calcRoundScores $(thisround)$>>>calc round scores</$button>
</$let>

and the magic is invoked below in $:/user/tournix/macros/calcScores

\define calcScores(result)
<<callForgedMethod targetMethod:calcScores extraName:result extraValue:"""$result$""">>
\end

Not also that I simplified my naming convention for the rules + scoring system related macros, but that’s just a detail. I went from $:/user/dba/macros/scoring/420sodomos/dba_420sodomos_calcScores first to $:/user/dba/macros/scoring/420sodomos/dba_420sodomos_calcScores and finally to $:/user/dba/420sodomos/macros/_calcScores for instance.