Accessing the title passed into a function?

Folks, Despite becoming quite familiar with the new function pragma there is a little gap I would like to fill. Is it in my understanding or does it point to a need for an improvement?

Consider the following

\function contains.string(string) [split<string>count[]compare:number:gt[1]]

{{{ [[This is a tiddler 2 that ]contains.string[tiddler]] }}}

The following custom filter splits the incoming title with the string and counts the parts. If there are more than one part then the string must exist in the incoming title. Noting that even if the title has a leading or trailing string this still works.

  • The custom operator will output the count if its greater than 1

Your will note that the custom operator is simply invoked with the input title. As far as I can tell there is no way to access this input title except to act on it with a filter. Our functions are by default what we call ā€œselection modifiersā€, it is quite easy to ignore the incoming title by using a filter that is a ā€œselection constructorā€ such as starting with a variable [<varname>....

However

In the above function I would like to return the incoming title if the count is greater than 1 but I do not seem to have any way to reference this title, it seems to have being lost in the filter.

  • Common variables or fieldnames do not seem to work such as currentTiddler, title or {!!title} as these all refer to the current tiddler, not the current title in the filter.

There are other cases where I would like to have multiple filter runs each of which is applied to the incoming title, this is possible to some extent.

Also consider the following

\function test.function(string) [prefix[test]] [suffix[test]]

{{{ [[a title test]test.function[test]] }}}
{{{ [[test a title]test.function[test]] }}}
  • You will see each filter run, the prefix and suffix test, is handed the incoming title.

However if try to do anything else with the incoming title I loose access to it.

How can we otherwise access the incoming title, if we want to return that title or perform additional operations on it?

Might it be simpler to use
\function contains.string(string) [search:title:literal,casesensitive<string>]?

Re: your broader question: this is the sort of situation in which I normally use filter or :filterā€”which is, I think, the normal Tiddlywiki way to return an input title unchanged. Notably, :filter works with functions, so you could do something like this:

\function contains.string(string) [split<string>count[]compare:number:gt[1]]

{{{ [[This is a tiddler 2 that ]] :filter[contains.string[tiddler]] }}}

Thanks @etardiff I suppose you have given me a ā€œwork aroundā€, but I still think the question still stands to be answered.

  • I want a general solution to referencing the incoming title, the example given is primarily to illustrate the issue.
  • In someways the inability for the function to return the title, forces the way we write the calling filter, and depending on what that calling filter is trying to do it may add complexity, if not actually making it impossible to write.
  • Thanks, yes. I think what happens here is the abstraction of title into a literal string when the ā€œtitleā€ is in fact a logical title, although ā€œtitleā€ is listed in the ā€œfield listā€, kind of conceals this approach. That is perhaps why I did not find it when looking.

You would be aware that some of the filter runs allow reference to both currentTiddler and ..currentTiddler, and I am thinking along those lines for inside functions.

So just for clarity, Using the search operator of @etardiff has the benefit that when found it does return the title passed into the filter. So the following is an effective work around for the original example;

\function has.string(string) [search:title:literal,casesensitive<string>]

 {{{ [[This is a tiddler 2 that ]has.string[ 2 ]] }}}
  • In this case if the test is passed the input title is returned, so there is no need to reference it in the function, it just passes through if the string is found.

But to be clear

I am still asking how can we reference the ā€œcurrent titleā€ inside the function.

  • There may be cases where we canā€™t use the method above
  • There will be cases where referencing the input title in a function, makes it simpler to write a function or have it self document.

Not a pretty example, but the :map[[]] prefix accesses the input title and could get you somewhereā€¦

\function contains.string(string:"this_is_an_unlikely_title_123?!") [split<string>count[]compare:number:gt[1]]

{{{ [[This is a tiddler 2 that ]] :map[contains.string[tiddler]then<currentTiddler>] }}}
1 Like

That is true but I want that title available in the function, not the filter that calls the function.

  • Perhaps I can make a new utility function that returns the title using the map, that I can use in the function definition.
  • But all this is to get what I think is a missing feature in functions.

I imagin a new variable lets call it ā€œfunction-titleā€, that is somehow populated with the title it is given.

[split<string>count[]compare:number:gt[1]then<function-title>]
  • In this case if greater than 1 it returns the title else nothing.

Thankis to all above, I just found a solution. A very simple function that has access to the current title

\function function.title() [!is[blank]]

\function contains.string(string) [split<string>count[]compare:number:gt[1]then<function.title>] 


{{{ [[This is a tiddler that contains 2]contains.string[2]] }}}

This is in fact the magic variable I am after;

  • So now if the string is found it returns the title in the function.

Conclusion

The specific example I gave is best solved with @etardiffā€™s use of

\function contains.string(string) [search:title:literal,casesensitive<string>]

However the problem I was trying to illustrate can be solved with;

\function function.title() [!is[blank]]

Which gives us access to the ā€œcurrent titleā€ in the function.

  • ā€œNot is blankā€, is only a test and passes the title through, in this case we donā€™t care if it is blank.
  • Arguably function.title is a good utility function to have available for such cases.

Question

Answer

Define the utility function;

\function function.title() [!is[blank]]

and use <function.title> where required in your functions to retrieve the ā€œcurrent titleā€ in use within the function.

1 Like

Hi @TW_Tones

Thanks for this thread and your solution :slightly_smiling_face:
For your function.title function you could use all[] as itā€™s a ā€œpass-throughā€ with its input.

\function function.title() [all[]]

Also I donā€™t know if itā€™s related to your topic or more specifically to your example, but contains.string behaves weirdly when itā€™s passed more than one input string:

\function function.title() [all[]]
\function contains.string(string) [split<string>count[]compare:number:gt[1]then<function.title>] 


{{{ [tag[HelloThere]contains.string[e]] }}}

This example only returns the first matching input title. The culprit is the count operator, which counts results after split has been applied to every input, not separately for each input.

Just like your initial topic request, I would also like to have a currentInput or currentItem variable available inside a function definition. IMHO it would be consistent with currentTiddler and ..currentTiddler variables inside filter runs.

Fred

Thaks fred for your observations. I expected to see more variations of function.title and I suppose different arguments for which is more suitable. I think this supports the argument for a special variable to be made available. ā€˜currentItemā€™ is a good one.

I think you will find the weirdness is actualy how the tripple curly braces, or filtered transclusion operates, as well as when you reference the function as a variable '<<contains.string>>` but not when the function is used inside a filter= parameter.

  • this first value is a ā€œfeatureā€, which I was not happy with when I first learned about it.
  • there are ways around this, like ending the function with +[join[ ]] or +[format:titlelist[]join[ ]] to set the custom operator value as a list. You may need to enlist this to use it.

I think you will find the count is done for each title, but without the above you only get to see the first result.

Try it and test what I am saying, I appreciate feedback.

I tried this on tiddlywiki.com:

\function function.title() [all[]]
\function contains.string(string) [split<string>count[]compare:number:gt[1]then<function.title>] 


{{{ [tag[HelloThere]contains.string[e]count[]] }}}

(notice the added count operator in the filtered transclusion) and the result is 1, so I guess itā€™s not related to triple curly braces.

Hereā€™s a [edit] useless and wrong [/edit] variant which returns a compliant result, but using 2 filter runs:

\function function.title() [all[]]
\function contains.string(string) [all[]] :filter[split<string>count[]compare:number:gt[1]then<function.title>] 


{{{ [tag[HelloThere]function.title[]] }}}

---

{{{ [tag[HelloThere]contains.string[p]] }}}

Result on tiddlywiki.com:

Fred

This example is wrong, as the :filter filter run doesnā€™t use the then<...> part of the filter.
So contains.string can be shortened to:

\function contains.string(string) [all[]] :filter[split<string>count[]compare:number:gt[1]] 

which is useless for OP. :frowning_face:

Fred

Why put all in the contains.string? Functions act on each title given to them. All is a filter constructor and generates a new list.

I will have to respond tomorrow. Its late here, 2am, but i think you are making some subtle errors. That is off course how we learn :nerd_face:

@TW_Tones: Thank you for bringing up and finding a solution for a problem thatā€™s bothered me as well.

If we were going to propose a fix to core to ensure that thereā€™s always a variable available, would we be able to reuse currentTiddler? Or is there some reason that wouldnā€™t be a good choice? If not, would there be any objection to @tw-FRedā€™s currentInput or currentItem?

Note that when a function is used in a filter run such as [tag[HelloThere]my.function[]], it is invoked exactly once and is passed all previous input titles. This is by design so that functions can also be written to be reductive if need be, which would not be possible if functions were invoked once for each input title. As such, there is no relevant value that can be assigned as currentTiddler since we are dealing with multiple input titles, which as pointed out above can already be accessed through all[].

In contrast, filter run prefixes such as :map, :reduce and :filter or the filter[] operator are invoked once per input title, which allows the variable currentTiddler to be set to the corresponding title each time.

5 Likes

Your original example contained [!is[blank]], which filters out blank values. As stated in TW documentation for all operator:

As a special case, if the parameter is empty, the output is simply a copy of the input.

This makes all[] some kind of ā€œidentity operatorā€, simply copying its input to its output, without filtering blank values.
Thus, {{{ [[]] [[A]] +[all[]count[]] }}} returns 2 where {{{ [[]] [[A]] +[!is[blank]count[]] }}} returns 1.

Fred

@tw-FRed I believe this is what you were aiming for:

\function contains.string(string) [all[]] :filter[<currentTiddler>split<string>count[]compare:number:gt[1]]
1 Like
\function contains.string(string) [split<string>count[]compare:number:gt[1]]

{{{ [[This is a tiddler 2 that ]]  :filter[contains.string[tiddler]] }}}

Scott, I think @saqimtiaz more than covers the use of currentTiddler there but to put it simply, it is a variable that already has a use in functions. The title I was after is they one passed into a function.

  • the title(s) are available to the function I just had no way to refer to it later in the function.

No objection to currentItem here. However the current solution is function.title which only works because its a custom function containing a period in its name.

  • I am now tempted to document this approach and not seek a special variable unless its trivial to implement.

@tw-FRed a new day here and is will respond to your doubt about my assertion;

  • however your used a different custom function than the one I published as the answer. This has become the subject of subsequent replies.
  • however the [all[]] behaves in a function, I still think [!is[blank]] is more to the point because it implies a simple test if a non-blank value exists in function.title. The introduction of [all[]... implies a whole new list.

The way I think of this is it is a single filter run, that commences with a list of titles, all the titles are then given one at a time to the custom filter operator.

  • I totaly agree with your point here, I would just ā€œword itā€ a little differently for a broader audience.

Thanks all for your contributions.

I am a little confused, could you please illustrate and explain again, what you think is weird a fresh? so we can run with that?

Sorry, must have being due to the late hours, I should have stated this as;

I think you will find the weirdness is actually how the triple curly braces, when used as an attribute, or filtered transclusion operates. Returning the first value only.

This illustrates the point I was making;

\function contains.string(string) [search:title:literal,caseinsensitive<string>]
\function function.title() [!is[blank]]
\function string.titles(string) [search:title:literal,caseinsensitive<string>count[]]
\function top.toc.contains(string) [tag[TableOfContents]contains.string<string>]

# {{{ [tag[TableOfContents]contains.string[t]] }}} returns list
# <$text text={{{ [tag[TableOfContents]contains.string[t]] }}}/> < Returns first only
# <<top.toc.contains t>> < Returns first only
# <$list filter=<<top.toc.contains t>> >

</$list>  < Returns first only
# <$list filter="[top.toc.contains[t]]" >

</$list> < returns list

But perhaps your case if different?

@TW_Tones

Your explanations and Saqā€™s ones helped me to understand the power and limits of functions.
Now is time for me to get some rest :slightly_smiling_face:

Fred