Over the weekend I’ve been working on a new PR that implements an apparently fundamental change to TiddlyWiki’s internal design: variables may now contain not just a single value, but a list of zero or more items.
In practice, this change is considerably less radical than one might think: for full backwards compatibility, all the existing usages of variables in the core just use the first item in the list.
Nothing actually changes apart from a handful of new features that expose the true nature of variables:
- A new
:let
filter run prefix that assigns the current result list to a variable whose name is taken from the first value returned by the filter run itself. The variable can be used in the rest of the filter expression (and any subfilters that it may invoke) - Extended the existing
<$let>
widget to assign multi-valued variables. This is backwards compatible because the existing ways of accessing these variables will still only see a single text value - A new
varlist[name]
that retrieves the value of a variable as a list. It works like theall[]
operator in that it replaces the input list with the new list. If the variable only contains a single value then a list of one item is returned
There is a preview build available.
There are further improvements that could be made. An example is that it gives us a way to reduce our dependence on title lists in many situations. Title lists are brittle and cannot properly express some items. For example, it is not possible to store a string like ]]
in a title list.
The <$draggable>
sets a variable called actionTiddler
containing a title list of the dragged titles that is accessible within the associated actions. We could now provide an alternative variable that made the list of dragged titles available as a list variable.
What Problems Does This Change Solve?
There are a few common scenarios that these new capabilities can make much simpler.
Nested Let Widgets
A common technique today is to break up complex filters using a series of assignments. In this example we use an intermediate variable “category” to compute a value that is then used as a parameter to “jsonget”:
<$let
category={{{ [tf.makecat<currentTiddler>,[8]] }}}
subcategory={{{ [{schema}jsonget[root],<category>] }}}
>
...
</$let>
This approach (or a similar one) is needed to get around a fundamental limitation that it is not directly possible to use a the result of a filter as the parameter to a subsequent filter operator. We are limited to parameters that are transclusions, or function/macro calls with fixed parameters.
With the changes here we can use a single filter to achieve the same result:
<$let
subcategory={{{ [tf.makecat<currentTiddler>,[8]] :let[[category]] [{schema}jsonget[root],<category>] }}}
>
...
</$let>
All-in-one Filters
Generally, the advantage of being able to shoehorn a complex, multistep filter into a single filter expression is to be able to use them in the situations that only cater for a single filter. Cascades are an example of this. The example that drove the development of this PR was the new colour handling improvements which redefines palette entries as filters, instead of wikitext as at present.
Background
I’ve been thinking about the ability to assign variables within a filter for a long time; my first notes on the subject are from 2021. It was only after finally implementing the feature on Friday that it became clear that there would be value in being able to store a list in a variable.
As it has turned out, the work has been remarkably straightforward, largely because of the way that the internal implementation of function calls actually treats them as a parameterised variable that returns a list instead of a single item.
Open Questions
I am interested in any feedback, and welcome any questions or thoughts. There are a couple of open questions that have come to my mind:
- The names
:let
andvarlist[]
are poor, and we need to come up with better terminology that is more distinct from existing usages - Do we need further terminology to make the documentation clearer? For example, a term for a variable that contains a list versus a variable that contains a single item. The terminology needs to make clear that these are exactly the same type of variable, but with different values; they are not different types of variable