Learning from my mistakes: Logical AND

Here is a subtle mistake that I just spent hours debugging (finding, really). Maybe this will help someone not to repeat that.

When combining two conditions in a filter, I often used something like this:

<$list filter="[«condition1»] :all[«condition2»] :and[nth[2]]" variable="void">

The conditions were written such that they returned exactly one item when true (with e.g. a then[yes] at the end) and nothing when false.

When both conditions were true, there would exist two input list items into the :and prefixed filter run, so that nth[2] returned the latter and the $list content was rendered. Setting the variable to void avoids unintentionally messing with currentTiddler (another mistake that I made often early on).

However, when «condition2» is such that a title is returned, and that title changed, the above code would lead to the whole $list content being destroyed and re-rendered, because the value of the filter attribute has changed, even though the logical result that both conditions are true has not. Having something like this in a PageTemplate can lead to very weird navigational behavior.

Anyway, the solution is to simply count the number of items in the list like so:

<$list filter="[«condition1»] :all[«condition2»] :and[count[]match[2]]" variable="void">

where the match[] parameter should match the number of conditions one wants to be true. As long as both conditions remain true, the filter output does not change and the $list widget does not destroy or create any DOM elements.

Have a nice day
Yaisog

PS: The :all filter run prefix prevents automatic de-duplication if both conditions return the same item, e.g. a yes string, in which case match[2] would erroneously not be true. Another mistake I’ve done before.

PPS: The same way one could also implement other logic (updated):

  • NOR: count[]match[0]
  • OR: count[]!match[0]
  • XOR: count[]match[1]
  • XNOR: count[]!match[1]
  • AND: count[]match[2]
  • NAND: count[]!match[2]
5 Likes

Hi, It’s nice that you post this info, but it’s way to complex to understand without concrete = working examples that can be copy/pasted to a wiki to play with.

«condition1», «condition"2» are placeholders, but they are the important content that is needed to actually understand your problems / solutions?

1 Like

I’d love to see these logical ops appear in a plugin. :heart_eyes: :sunglasses:

Logical operation in TiddlyWiki filter language is quite interesting! Your approach with counting the results of previous two conditions is intresting!

I would like to add these two references on logical operators.

The below page also shows the visual meaning of different logical operators:

What is logic gate (AND, OR, XOR, NOT, NAND, NOR and XNOR)? | Definition from TechTarget

Is :and correct here or :all? It seems with :all it does not work!

See

Enter a number: <$edit-text field=search/>
<$list filter="
[{!!search}compare:number:gt[10]then[first]]
[{!!search}compare:number:lt[20]then[second]]
:and[count[]match[2]]
" variable="void">

Both conditions were met! <<void>>

</$list>

Only :and works!

The :all is confusing! It seems it does not act as a real filter run prefix in which I expect previous run result pass into it!

This reminds me of a searchability gap in official documentation.

I remember struggling mightily to construct a filter with “or” behavior, early on.

Tiddlywiki.com had nothing for “disjunction” (my first choice), nothing useful for “boolean” (my second choice) – and of course “or” is too short as a search string. Nothing useful on NOR / NAND / XOR either. “Logical operators” as a search yields “Conditional operators” but nothing else.

Some initial results for the terms that novices would reach for (whether they’re coming from excel, or filemaker, or boolean library catalog searches, etc.) — with a translation / redirection to how TiddlyWiki handles that stuff — would be a great addition to tiddlywiki.com.

-Springer

1 Like

This might be the impetus I need to finally tackle the new functionality from parameterized transclusions. From what I read so far it should be possible to create a widget that takes two filters and an operator to combine them and apart from that works mostly like a $list widget…

Hi @Mohammad, the :all simply avoids de-duplication and is needed only for the second condition. Imagine that the result of the first run is [[yes]] and that of the second run is also [[yes]]. Then,

[[yes]] [[yes]] :and[count[]match[2]]

would yield an empty list, because the yesses are de-duplicated, so their count is 1. With the :all this does not happen and

[[yes]] :all[[yes]] :and[count[]match[2]]

results in “2” and thus a non-empty list (which equals “true”). See also the tables in https://tiddlywiki.com/#Filter%20Expression.

1 Like

I was thinking that those who might run into the particular trap that I described above would know what those conditions mean, and that all others don’t need to worry about these nuances of filter language.

Anyway, here is a contrived example that I was able to come up with. Let’s assume that there is a config option via a tiddler $:/config/yaisog/showEmptyStoryHint to show a particular tiddler $:/yaisog/emptyStoryContent when the StoryList is empty and the config option is set to yes. The corresponding $list could look like this:

<$list filter="[list[$:/StoryList]then[yes]] :all[{$:/config/yaisog/showEmptyStoryHint}match[yes]] :and[count[]match[2]]" variable="void">
    <$transclude tiddler="$:/yaisog/emptyStoryContent" />
</$list>

I did not test the code as this is a made-up example. Let me know if there are any syntax typos.

There is actually a more concise way to write the expressions involving the compare[] operator:

  • OR: count[]compare:number:gt[0]count[]!match[0]
  • XNOR: count[]compare:number:ne[1]count[]!match[1]
  • NAND: count[]compare:number:lt[2]count[]!match[2]

So symmetric. :heart_eyes:
I updated the list in the OP correspondingly.

1 Like

Hi @CodaCoder, I’ve been thinking a bit about how this could be implemented…

The idea with a new widget seems somewhat inflexible, as it would be limited to two conditions and a logical operator, e.g. “(A AND B)”:

<$boolean filter1="«condition1»" filter="«condition2»" operator="AND">
«content»
</$boolean>

There would be no easy way to do something like “A and (B or C)”, as widgets would not be nestable.

A macro might be possible to nest, but the «content» would have to be passed as a parameter? Urgh.

So it would have to be something extending the filter functionality. The first thought is obviously to use a shiny new filter function, but then both conditions would have to be put into macros in order to be passed (because filter syntax cannot be passed literally), maybe like so:

\define condition1() «filter expression»
\define condition2() «filter expression»

\function boolean(filter1:"",filter2:"",op:"AND")
[subfilter<filter1>then[true]] :all[subfilter<filter2>then[true]] :and[function[operation],<op>]
\end [<function-name>]

{{{ [function[boolean],<condition1>,<condition2>,[AND]] }}}

where operation is another custom function that somehow chooses the correct match[] operator depending on the value of op. I have no idea if this could work. If it did, it would be nestable, as filter2 could contain e.g. the boolean operation “(B or C)” via the same new boolean function.

However, I believe the most elegant way to tackle this would be a filter run prefix like so:

[«condition1»] :boolean:and[«condition2»]

If needed, «condition2» could contain a subfilter[] operator that implements a nested boolean operation. Filter run prefixes are great when the parameter should be a filter expression. Then, both conditions can be entered directly in their respective runs without the need for additional macro definitions.

Or, maybe there exists another approach with the newfangled transclusions that I didn’t see yet?

I’d be inclined to try my hand at a filter prefix implementation. However, given that my JavaScript skills are primarily based on copy-paste-debug, I’m not sure if I’ll get there…

Much as I thought/imagined.

Cool.

Even cooler :sunglasses:

The JS should be straightforward, but I would flounder on the filter-prefix: stuff…

Hi @CodaCoder, the whole thing was actually much easier than I thought. The code for the run prefix is here:
boolean.js.tid (1.9 KB)
Just import this file into a wiki, save and reload. It works as described above.

[«condition1»] :boolean:and[«condition2»]

I also tested this:

\define subcondition() [«condition2»] :boolean:or[«condition3»]

{{{ [«condition1»] :boolean:and[subfilter<subcondition>] }}}

which I exceedingly like. :grinning_face_with_smiling_eyes:

A run that returns any result is considered true in the context of this run prefix. The :boolean run will return either the string “true” or nothing. The «condition»s are arbitrary filter runs. Instead of the suffix and you can also use or, xor, nand, nor, xnor – the last three are only there for completeness, really, and because it’s a bit clumsy to try to “invert” a filter run result.

I’ll whip up a doc tiddler with some examples over the weekend, hopefully. I’ll also propose a PR to the core, but if that was not welcome, I’ll make a proper plugin.

Have a nice day
Yaisog

1 Like

I exceedingly agree :nerd_face:

Kudos!

This is actually where all the copy-pasting is happening… :blush:

These tidbits are too valuable to risk being lost in the stream of posts. We should probably have some official and general doc tids on “logic” constructs where this would fit in. Logic constructs are naturally needed in TW, and probably expected by many coders coming here.

I don’t have a solid enough grasp of these things but for anyone that has, do go ahead and formulate a doc. IMO it can be limited to some specific logic operator (e.g AND) that shows how to construct it as a filter expression and what to pay attention to (like the OP here). Once the first such doc is formulated, I’d expect it to be simpler to create the next one, and eventually an encompassing tid titled “Boolean filter expressions” or some such.

2 Likes