Inverting a function used in a filter

If I have a function like this:

\function has.picture() [get[avatar]!match[empty.png]]

I can use it in a filter like this:


[tag[Member]has.picture[]]

(oops, see Springer’s initial response!)

or like

[tag[Member]] :filter[has.picture[]]

But is there a clear way to invert the behavior, using this function? That is, I want to collect the list of Members who don’t have a non-empty image, and I’d love to reuse this function to do so.

I know I can simply create another function, and that’s what I’ve done for now (\function has.no.picture() [get[avatar]match[empty.png]].) But I’m wondering why my initial approaches didn’t work.

I wasn’t particularly surprised when this didn’t work:

[tag[Member]!has.picture[]]
<!--        ^--------- NOTE the "!"   -->

But I was expecting this to work fine:

[tag[Member]] :except[has.picture[]]

or this:

[tag[Member]] -[tag[Member]has.picture[]]

or possibly this:

[tag[Member]] :filter[has.picture[]then[]else[yes]]

None of them work; they all return the full list of Members, even though a quarter of them are skipped when I use [get[Member]has.picture[]]. I can’t see why. Can someone explain?

I got the following filter acrobatics to work with a minimal example:

[tag[Member]] :filter[has.picture[]else[HasNoPicture]match[HasNoPicture]]

but I’m totally not satisfied with it, of course.

I think I had a similar problem recently and I ended up creating the negated function as well, or going some other way around.

Negating custom functions or a flag for the filter filter run prefix like :filter:negate[] would be a great convenience.

Something seems fishy to me. Your function should return not the tiddlers with non-empty avatars, but those avatar values (leaving out those that are empty.png).

So it’s no surprise to me that it doesn’t easily work to remove the member tiddlers with pictures; it should only remove tiddlers named identically to the avatar values. The surprise, to me, is that you report that your filter (as pasted above) does work for you to generate the list of member tiddlers. I would think to get the member tiddler list (with non-default member avatar values), you’d need

{{{ [tag[member]] :filter[has.picture[]] }}}

Thanks! That definitely feels odd, but I guess it works.

I thought that was what :except was for, and its - shorthand. But it doesn’t work the way I expect it to. (Maybe it’s a tongue-twister issue: To expect that except works one way evetually leads you to accept that it simply doesn’t! :slightly_smiling_face: )

You’re absolutely right. That version does return the avatars. I am actually using the other form which works correctly, and only thought of the smaller form as I wrote this question, and simply didn’t notice the change from

to

I was focused on counts instead! In any case, the main question still remains, albeit about the second form,

[tag[Member]] :filter[has.picture[]]

:except starts with no input and removes its results from the previous results.

A B C AB :except[[B]]  --> A C AB

:filter gets previous results as its input and lets only those through, for which there is an non-empty result.

A B C AB :filter[prefix[A]]  --> A AB

The :filter:negate I’d wish for, would let only those for which there is no result, so

A B C AB :filter:negate[prefix[A]]  --> B C

Of course in this example it could be simply done by negating the prefix function, but assuming we can’t do that for custom functions or don’t want to do that, this would be handy.

If what you’re really after is the member list (not the list of avatar values), why not just make your function track that. Then you can omit those values. Function would be:

\function has.picture() [has[avatar]] :filter[get[avatar]!match[empty.png]]

and getting the “complementary sets” of member tiddlers would be as simple as you clearly want it to be:

{{{ [tag[member]has.picture[]] }}}

{{{ [tag[member]] -[has.picture[]] }}}

(And of course it’s easy to get from any member tiddler list to your actual avatar values. Not as easy to go the other direction!)

(EDIT: Not so much for this case, but for other thread followers: I think I’ve learned to avoid invoking a “get” in any function or reusable filter without passing through a “has” gate beforehand. Otherwise if you reuse chunks of code in a new context the “get” could net you some empty values that turn up strange matches.)

1 Like

Elsewhere I’ve done that with a reject function, identical to filter except that the returned boolean has the opposite effect. I agree, a :negate suffix would do fine, as would a new operator. It would definitely be nice to have!

That works great. But I want this function to be used as a filter. One use-case looks like this:

 [tag[Member]] :filter[has[lichess-id]] :filter[has.picture[]] +[nth<index>] 

So I’m not filtering with has.picture over all Members (or all avatar-holders), but a subset of them, after another filter has operated. In another place, I use all Members, and I could easily imagine others.

So my main question is whether there’s a straightforward way to invert the use of a function as a filter.

My actual function is more complex, looking something more like

\function has.picture() [get[avatar]!match[empty.png]!match[another.png]]

And I was looking at how I’d invert that manually, and was getting stuck, when it hit me that I just wanted to invert the filter, not the function it calls. A bunch of things didn’t work, and I really didn’t quite understand why. I’m still confused, but I’ve gotten some new techniques, plus a little schooling about checking the actual output and not just the counts! :wink: Thank you for both!

If you provided a sample of test data, or chose an example that can be tested on tiddlywiki.com, I would be happy to help, I have successfully made functions that allow negation previously.

  • At least right now it is a big ask for me to build the test case to validate a solution.

While I was hoping this was clear enough to stand alone, it grew out of wanting to reuse a function in http://scott.sauyet.com/Tiddlywiki/Demo/ClubDemo/v2/. The function is in $:/_/my/functions/has.picture, and you can see it used in Members (which is included in a card on the default first tiddler, Welcome.) The data is a little out-of-date, and partially anonymized. Links to content inside GigantiCorp won’t function from outside, but should be irrelevant here.) You can find it in the definition of currentTiddler in the Spotlight section. This section has some additional complexity not important to this discussion, but if you want to know, it’s designed to have a Member of the Day, feel, with a random-ish selection. But you can also test it in Advanced Search > Filter, with something like

[tag[Member]] :filter[has.picture[]]

I was planning on building additional reporting, and I wanted to invert the function so I could report all those Members for whom we don’t have an avatar. (The current mess of two different icons for missing avatars makes sense for how the data was sourced, but I will probably combine them all into one at some point.)

This is the actual current function:

\function has.picture() [get[avatar]!match[empty.png]!match[Icon-round-Question_mark.svg.png]]

(If the lack of upcoming meetings in the wiki seems strange, note that this is a two-week old copy.)

1 Like