Dynamically calling a macro

Still hard at work on my Periodic Table demo, I’m working on an idea that would be much easier if I could supply one macro as a parameter to another one. (For programmers, this would be something like higher-order functions, being able to pass functions as arguments to other functions.)

There may well be a different technique easily available to do the same thing. I’m not picky, just looking for appropriate mechanisms.

The main tiddler shows a periodic table; I would like to abstract it into a macro, and alter that so that I can do different things. You’ll notice that there is a fair bit of white space in the table (two tile wide by two tile high blocks in the bottom left and bottom right, a 10 x 3 block in the top center, a 5 x 1 block to the left of He(lium) and a 1 x 1 block to the right of H(ydrogen). In various situations, I would like to fill this with different content. I might want a flash-card in the largest section, asking you to find a random element in an (unlabeled) version of the table. I might want to add an Element-type list in the bottom-right, in which hovering/long pressing the Noble Gas link deemphasizes all the elements except those seven Nobel Gas elements. I can imagine many things. They all have in common the generation of the table with a call to the current tile macro or a different one for their content.

So my thought was that I might call this like

<<make-table tile:"normal-tile">>

for the existing table – labeling those empty blocks as “northwest”, “north”, “northeast”, “soutthwest”, “southeast” – I might imagine a flashcard game like

<<make-table tile:"blank-colored-tile", north:"flashcard-game">>

and perhaps something like:

<<make-table tile:"normal-tile" southeast:"element-type-hover">>

and so so, where, "normal-tile", "blank-colored-tile", "flashcard-game", and "element-type-hover" are all names of macros I would want to insert at the appropriate locations.

Is this possible? Is there a way to include macros whose names are passed as arguments? Or is there another way to make direct reference to other macros in the maco-call syntax? Any other suggestions for accomplishing this?

1 Like

Yes and its trivial, both I and @Mohammad (If I remember correctly) realised it was so easy years ago, but have not had much cause to use it.

  • More often I have used it to pass a variable name (not very different to a macro name) because its easier to have a macro parameter of “varname” than try and pass the value <<varname>>
\define othermacro() do something
\define macro(submacro)
<$macrocall $name="$submacro$"/>
\end
<<macro othermacro>>
  • Of course you need to avoid calling yourself unless there is a limit to the number of times (as in recursive macros)
  • if you use a non existent macroname if fails gracefully
  • You could also test the parameter to see if it is one of an allowed set of values/macro names

Alternatively, just use previously set variables as the macro name

\define normal-tile() do something
\define display-it()
<$macrocall $name="$(display-macro)$"/>
even <<$(display-macro)$>> may work
\end
<$let display-macro="normal-tile">
<<display-it>>
</$let>

I expect I can share more info but is this enough for you to work with?

1 Like

Very much so. I keep forgetting that <<macro...>> is just a shortcut for <$macrocall ...>.

That’s certainly enough to get me going. I thought this was going to be a difficult process, but it looks trivial.

Thank you as always for your help.

TLDR: Can a macro call one of the macros supplied to it as named parameters using a variable holding the name of that parameter?


I’m finally getting around to this work that brought up this question some weeks back, and I’ve hit a snag. It turns our what I really need is to use a locally-defined variable from a <$let...> call to choose one of the named parameters to my macro.

I would call the macro with something like

<<periodic-table tile:“complete-tile-link” northeast:“test-tile”>>

where complete-tile-link and test-tile are global macros.

then inside the periodic-table macro, I scan through a JSON array of arrays describing the table. Most values in there are simply the name of elements, and they are handled fine. But in some circumstances, the name looks like north-10-3, which gives me a block name (‘north’) a colspan (‘10’) and a rowspan (‘3’). Of course I use the rowspan and colspan to set properties on the current <td ...>. But the trick would be now to use the block property to call the correct macro from the list of parameters passed in. The macro looks like this:

\define periodic-table(
  northwest:blank-tile
  north:blank-tile 
  northeast:blank-tile
  southwest:blank-tile 
  southeast:blank-tile
  tile:complete-tile-link
)
<!-- ... -->
\end

And the call inside it looks like this:

<$let
  block={{{ [all[current]split[-]nth[1]] }}}
  cols={{{ [all[current]split[-]nth[2]] }}}
  rows={{{ [all[current]split[-]nth[3]] }}}
> <!-- this works properly to define my variables -->
<td colspan=<<cols>> rowspan=<<rows>> class=<<block>> >
  <<block>> <!-- this line just to demonstrate that the variable is correctly defined -->
  <$macrocall $name="$(block)$"/>
         <!--       `---------'        -->
         <!--     what can go here?    -->
</td>
</$let>

I’ve tried many different syntaxes in the spot indicated, and haven’t found one that works. The one here is simply the last one I tried before going to bed.

(Note that this is for only five of the 123 cells; the others are covered by another branch which is working perfectly, calling

<$macrocall $name=$tile$ />

), but here we can hard-code the string "tile". In the problem spot, I want to indirectly refer to the name of one of the parameters. How can I do that?

You can play with this at Chemistry — periodic table and related tools. The placeholder strings showing the names of the empty blocks are just for demo purposes. If I get it to work there should be extra text in the top-right of the table, “This space intentionally left blank.”

The three important tiddlers are


I do have two work-arounds in case there simply is no syntax that will work here: First I can use a series of five filters for the five possible values, testing my block parameter against each one and making separate <$macro-call ...>s in each case. It’s feels untidy, and if at some point I want to offer other formats of the table, it could get uglier. But it’s doable. Second, I could skip the external JSON altogether, and hardcode the full table structure in the macro. Again, this feels less extensible, but with only 123 cells (118 elements and 5 empty blocks), it’s doable.

But I would rather do neither of these.


So the basic question is Can a macro call one of the macros supplied to it as named parameters using a variable holding the name of that parameter?

Have you tried:

<$macrocall $name=<<block>>/>

Yes, that was the very first thing I tried. I don’t have the full list available to me right now of what I did try, but there were a number of attempts, including that one.

The first workaround I mentioned does work:

  <$list filter="[<block>compare:string:eq[northwest]]" >
    <$macrocall $name=$northwest$ />
  </$list><$list filter="[<block>compare:string:eq[north]]" >
    <$macrocall $name=$north$ />
  </$list><$list filter="[<block>compare:string:eq[northeast]]" >
    <$macrocall $name=$northeast$ />
  </$list><$list filter="[<block>compare:string:eq[southwest]]" >
    <$macrocall $name=$southwest$ />
  </$list><$list filter="[<block>compare:string:eq[southeast]]" >
    <$macrocall $name=$southeast$ />
  </$list>

But it feels ugly and fragile. If I do end up using it, is there any shorter equivalent to compare:string:eq? Just wondering.

1 Like

Try this:

<$list filter="[<block>match[north]]"    ><$macrocall $name=$north$/>    </$list>
<$list filter="[<block>match[northwest]]"><$macrocall $name=$northwest$/></$list>
<$list filter="[<block>match[northeast]]"><$macrocall $name=$northeast$/></$list>
<$list filter="[<block>match[southwest]]"><$macrocall $name=$southwest$/></$list>
<$list filter="[<block>match[southeast]]"><$macrocall $name=$southeast$/></$list>

Thanks. That is nicer. Still, I’d rather not do this at all. But I don’t know if TW even supports the sort of indirection I’m looking for here.

I am on the move so not able to give a more considered reply. I think you need to resolve the value of the variable to get the parameter name, and then in turn use the double underscore variable notation in macros for access to that parameter as a variable.

See the getvariable operator.

So something along these lines.
<$macrocall $name={{{ [<block>getvariable[]addsuffix[__]addprefix[__]] }}}/>

Here’s another approach:

<$let north="$north$"
northwest="$northwest$" northeast="$northeast$"
southwest="$southwest$" southeast="$southeast$">
<$macrocall $name={{{ [<block>getvariable[] }}}>

Notes:

  • First, the $let converts the macro parameters into variables
  • Then, the $macrocall uses the block value and the getvariable[] filter operator to fetch the corresponding parameter value and invoke it as a macro.

Thank you. I won’t be able to test until this evening (six hours from now), but this looks like exactly what I was looking for. I didn’t know getvariable existed.

Thank you very much for your help, especially when you’re on the move!

Thank you. Another interesting variant. If saq’s approach doesn’t work, I will try this. getvariable is clearly the thing I was really missing.

Thank you for your help, and you too, @saqimtiaz!

Saq’s solution will work… but with a small adjustment:

<$macrocall $name={{{ [<block>addsuffix[__]addprefix[__]getvariable[]] }}}/>

Note that the leading/trailing underscores need to be added to the <block> value before using getvariable[]

Also, this could be written as:

<$macrocall $name={{{ =[[__]] [<block>] =[[__]] +[join[]getvariable[]] }}}/>

or even:

<$macrocall $name={{{ =[[__]] =[[__]] +[join<block>getvariable[]] }}}/>
1 Like

Thank you, everyone. This worked great. You can see the results at Chemistry — periodic table and related tools. Now off to try to use this new feature!

I must say, at least this time of day, after my first coffee that this all seems unnessasairy complex, in responce to a larger requirement.

Whilst I answered this originaly;

  • I did so in a simple answer a yes confirmation and pointing out that the macrocall can be given a $name=<<varname>> etc…

However for the larger problem you are now describing I can’t but help thinking there is more to your requirement and other ways to achive what you are after.

  • Then I see this “some circumstances” where you are creating a compound name to carry additional parameters? I then see the definition of periodic-table which seems not more than a set of key value pairs, however these are encoded in the macros parameter definitions it is thus not “reusable”.

I wonder if you could restate this in a minimal example?

  • I can think of a whole host of methods that could help out here but I admit I can’t wrap my head around how you ended up in the place you are in, “my bad I am sure”.

Alternativly can you tell me if I am interpreting your intentions correctly.

  • Given a periodic table layed on a 2d table of cells, does not consume every cell due to its groupings, there exists other empty cells.
  • You want to be able to address one or more of these cells possibly extending over rows and columns to display content to annotate the table?
    • But you want these annotations stored in an existing “JSON array of arrays describing the table”.

My initial thought would be why not apply the “annotations” as overlays at an address on the same 2D table as elements, then you need not provide cols and rowspans it will simply overflow it’s cell.

  • You could even introduce a new kind of element, the annotation element to be placed on the table just as any other element is, but simply displays its text?

I didn’t explain the problem completely. Let me try here. But first, yes, there are plenty of ways to achieve what I want, I may eventually switch to another one.

I’m very happy with the table as laid out so far (modulo some minor tweaks). But I mean for that to be only one of many different views of the table. There are various different ways I might style each individual tiles, but much more importantly, there are ways I would want to fill the empty space. Here are three:

  • I would like a view that lets the user hover over various categories (the period, the group, or the element type [such as Nobel Gases, Metals, etc.]) and have the table dim all the entries that do not belong to that group. So in the big section at the top, I would have a list of all eleven (appropriately-colored) element types. In the two sections at the bottom I would have a list of the seven periods and the eighteen groups. On hover or one of these labels, the view of the table would change to highlight them.

  • I would like to create a flash-card style training game. The elements would be colored just as they are now, and might have atomic number and mass, but would not show the element names or symbols. In the large northern empty block, I would have text showing what you’re looking for. “Find Copper”, then maybe after some time, an additional “Hint: it’s a Transition Metal”. The bottom two empty blocks would contain a countdown timer and a score. The northeast block might show responses to your current choice, such as “That’s Nickel, which is a close neighbor of Copper.” (I have many variants of this in my mind.)

  • I’ve been working offline with @telumire, who has a great demonstration of a Reactor,
    a drop-zone in the northern section. You drag elements in there, and it lists some compounds you can make using those elements. If you drag in just Hydrogen, it will suggest Dihydrogen (H2). If you then add Oxygen, it will offer you Water (H2O). And if you then drag in Carbon, it will offer a number of options, including Aspirin (C9H8O4). Obviously I would need to include tiddlers for a number of useful compounds, but I’ve already started working on this.

I have a number of other ideas as well. The thing they all have in common is that they fill the empty spaces of the table with additional information.

But there is another layout of the table which is not as convenient for viewing, but much more accurate, where the bottom two rows are fitted into their proper spots in between the second and third columns of the sixth and seventh rows. I would like to be able to easily support this layout as well, and to fill the gaps with other content as needed.

In order to support all this, I’m trying to abstract the table layout. Right now it’s in $:/_/my/data/table. (I’m still considering an alternate layout, $:/_/my/data/alt-table
, which is very pretty but probably less efficient to work with.) This declarative data is enough to let me lay the table out, including rowspans and colspans in the appropriate places.

But I also need a way to specify the content that goes in these blocks. Message #4 describes how that was intended to work. This thread has helped me get this idea working… You can see it in the latest version, 0.7.2, where the blocks contains silly messages.

So to create a new view (of this common-shaped table; I’ll need a slight extension for the wider, shorter version), we would just call the periodic-table macro passing the names of whatever macros we want to insert for any of the empty spaces.

That’s the big plan, and so far it seems to be working.

Yes, precisely.

That’s merely an implementation detail. As described above, I might have an alternative implementation of this layout information. Or quite possibly, I will hard-code the table layout inside the macro – which would then require two versions for the two fundamentally different layouts. But I would still use the techniques of this thread to fill the content of those blank spaces – here no getvariable would be needed, since I could reference the macro parameters directly where needed.

I would likely be quite unhappy with that. If you look at the silly note that I have in the northeast of the current version and start resizing the page, you will notice that the text scales with the change. I can do this because the northeast container serves as a layout container with a known size. Its children can declare their linear measurements in terms of that size. I don’t think I could do anything like that with overflowing cells.

This was a long response; I apologize. But please let me know if anything is still unclear.

And if you have further alternative suggestions for how to improve this, I’d love to hear them!

1 Like

Hmm, perthaps but then there is a lot of details, but you really are trying to build it in a fully featured way.

Some suggestions to use or throw away as desired, come to mind when dealing with such “multi-layered” “problems”. Layering or abstracting at a logical level, then using this to inform your code.

  • A general solution may be adding a layer, abstraction or dimension as needed.
  • I can imagine a “display table” say a 20x10 for which each display element will nominate where they belong. There would be the large set of “atomic elements” which have an attribute for there position on the “Display table” but similarly you could have another list of display elements, including annotations which also have an attrubute placing them on the “display table”.
    • You could expand this table to accomodate the extra elements in the column between 3 and 4 Lanthanides/Actinides, just flagging the other rows in these columns as empty.
    • Perhaps even a 3d table, where the Z axis is stored within display elements starting with the atomic elements.
  • To render the visible table you could include a filter of elements or sets of elements for which each is accessed to find its position on the display table.
  • One could also think of a concept of another layer that one could specify where to merge cells and rows, just provide the top left, and bottom right cell and the table display will use the content placed in the top left cell to populate the merged cells.
    • Something similar could be used for groups of elements, but you need one where every item in the set can be named eg the yellow element range, not all groups are rectangles.
  • With the multiple logical layers you could generate a tooltip containing information from more than one source
    • eg tooltip={{{ [<element>] [[in]] [<group>]] +[join[ ]] }}}

Interesting project @Scott_Sauyet if any code patterns or methods come to mind I will let you know.

Definitely. This started (as I imagine you’ll remember) as a demo of something interesting one could do with data tiddlers, beginning with ones you and @springer were using. But there was enough encouragement here to try to turn this into something useful for Chemistry students (and others). That’s the current goal, to make this into something a user could clone and customize, but with plenty of functionality built in.

Of course. That’s the Fundamental Theorem of Software Engineering. And it’s precisely the reason for that JSON table definition. I may still decide I don’t need it in that case, but it’s an important part of how I try to work.

That was one of my initial thoughts. I rejected it in favor of the current design for two reasons: The current one seems more flexible. And although my hope for this has faded, I was thinking that I would be able to generate the layout from the element properties directly, using things like period and group. But I didn’t want to add row/col data to the elements, as everything in those is meant to be meaningful on its own to chemists.

I’m curious to hear more about how you see this helping. While it sounds intriguing, I’m not able to come up with any practical uses.

I can’t really imagine how to build a table out of this in wikitext. I could definitely do it in JS, but I’m trying not to go there, at least not unless necessary. My current table definition makes this easy, since the row/colspans are part of the data. With another layer atop, I feel as though I would first have to build something like that JSON table, and then use it. If it turns out that this would be useful, I think that’s how I would go about this.

Tooltips are on my Ideas list, although I’d probably use something like tobibeer’s more sophisticated version rather than the built-in one.

Thank you for the great feedback!