Passing a parameter to a recursive callable in a filter

The following certainly cannot be ideal:

\procedure my-proc(my-param)
  <$wikify name="my-var" mode="inline" text=`<<my-helper $(my-param)$>>` >
    <!-- Use `my-var` here -->
  </$wikify>
\end my-proc

That call to wikify seems ridiculous. But when I replace it with a $let widget, I get problems. It doesn’t happen with the simplified version above1, but in my more complex recursive environment, I get a recursive transclusion error. I don’t get that with $wikify.

I have posted a sample wiki to http://scott.sauyet.com/Tiddlywiki/Demo/Breadcrumbs/v1/. You can see the effect I’m going for in, for example #Policy1234(II)(A):

The relevant code is in $:/_/my/procedures/crumbs

The (working!) function in question looks like this:

\procedure lineage(tid)
  <$wikify name="parent" mode="inline" text=`<<get-parent $(tid)$>>` >
    <% if [<parent>trim[]!match[]] %>
      <$transclude $variable="lineage" tid=<<parent>> />
    <% endif %> 
    <!-- ... more here ... -->
  </$wikify>
\end lineage

There’s a commented out version included, which looks instead like this:

\procedure lineage(tid)
  <$let parent={{{ [<get-parent>] }}} >
    <% if [<parent>trim[]!match[]] %>
      <$transclude $variable="lineage" tid=<<parent>> />
    <% endif %> 
    <!-- ... more here ... -->
  </$let>
\end lineage

This is the one that ends up with an error.

Can anyone suggest why?

Background

I am trying to redo the initial work I’ve one on a wiki I’ve been working on. But this time, instead of depending on parent and marker fields in the child tiddlers, I would like to do it all from the titles, which already describe a hierarchical structure: Policy1234(II)(A) is a child of Policy1234(II), which is itself a child of Policy1234, and where I need a marker, it’s in the final parenthesized section of the title. I think in the end that will be a lot cleaner. But because of the changed structure, it’s a bit of work. Before I went on to the main content, I decided to do these breadcrumbs first. I tried a number of ways to call to get the title, and kept failing until I tried this $wikify version. But I really feel as though there should be something simpler.




1 For instance this works as expected:

\procedure my-helper(tid)
  {{{ [<tid>uppercase[]] [[-]] [<tid>] +[join[]] }}}
\end my-helper

\procedure my-proc(my-param)
  <$let tid=<<my-param>> my-var={{{ [<my-helper>] }}} >
    The result looks like <<my-var>>.  Is that right?
  </$let>
\end my-proc

Just a passing comment, if you can replace a procedure/macro with a function /filter to generate the required result you will not need to wikify.

  • Even the lookup operator can help with transclusion

I started trying to create the relevant part as a function first. When I failed at that, I moved on to a procedure. I may try again.

How is that?

It is effectivly a transcluding filter operator, or more precisly it brings content from a title/field into the filter, but I supose {title!!fieldname} does that as well.

Perhaps it was premature for me to mention it, I was recently trying to hack things here. In my exploration I am prepared to “look outside the box”, especialy with recent enhancements such as functions and the backtick attributes, which are evaluated before the final render (wikification).

bump

I’m hoping someone might have an idea about this.

I’ve done a fair bit of recursive functions which work well, but never a good recursive procedure - sorry. I hope you figure it out!

The working version above is a demonstration that this is possible, as is my collatz implementation.

I’m revisiting this one because it popped up when I searched (for the umpteenth time) for a method to invoke a procedure inside a filter, passing it dynamic arguments. I keep wanting this, and keep getting stymied.

I’m not entirely sure what causes the recursive transclusion error — though I’ve encountered it before myself in situations that didn’t seem to necessitate it. But here are a couple of things that jumped out to me when looking at $:/_/my/procedures/crumbs in your sample wiki (recognizing, of course, that you may already have changed them in your production version):

  1. A missing space typo in your commented-out version: <$letparent={{{ [<get-parent>] }}} >
  • Personally, I’d use <$let parent=<<get-parent>> > for this sort of thing… am I missing some advantage to the filtered transclusion?
  1. Your definition $let parent={{{ [<get-parent>] }}} doesn’t pass a value for the tid parameter you’ve defined in \procedure get-parent(tid). I imagine you want get-parent to inherit <<tid>> from lineage, but this won’t work if the get-parent pragma definition includes a named parameter tid: when you use <<get-parent>>, <<tid>> will be redefined as an empty string. Here’s a very simple demo:
\procedure test(a) print <<a>>
\procedure outer(a) <<a>> <<test>>

\procedure test2() print <<a>>
\procedure outer2(a) <<a>> <<test2>>

<<test>>

<<test b>>

<$let a="a"><<test>></$let>

<<outer a>>

<<outer2 a>>

Instead, if you want access to a variable defined as the parameter of a parent procedure, you need to omit that parameter from the procedure definition: \procedure get-parent()

Of course, this only works if you’re not using <<get-parent MyTiddler>> as a stand-alone procedure-call…

Like Tony, my first inclination is to try to solve this with a function rather than a procedure. But I haven’t tested it extensively, so I may be missing some nuance. Does the following alternative work as expected?

\function get.parent()
[<tid>!prefix[Policy]then[]]
~[<tid>regexp<marker2>search-replace::regexp<marker2>,[$1]]
~[<tid>removeprefix[Policy]split[]first[]addsuffix[000]addprefix[Section]]
\end get.parent

\procedure lineage(tid)
  \whitespace trim
  <$let parent=<<get.parent>>>
    <% if [<parent>trim[]!match[]] %>
      <$transclude $variable="lineage" tid=<<parent>> />
    <% endif %> 
    <% if [<parent>trim[]!match[]] %>
      <div class="sep">&gt;</div>
    <% endif %>  
    <div class="crumb">
      <$link to=<<tid>> ><$view tiddler=<<tid>> field="caption"/></$link>
    </div>
  </$let>
\end lineage

When you use a procedure within a filter, it just treated as plain variable and its text content is used without any rendering/wikification.

If your procedure is simple enough, the plain text content is identical to the render output, it will appear to work.

I think this is the real core of your issue — procedures aren’t intended to be invoked inside filters! I’d actually argue that you shouldn’t be using a procedure to generate any simple text string (i.e. one that doesn’t involve widgets, HTML, or other wikitext). But they’re particularly unhelpful as variables in filters because (unlike functions) they don’t substitute their parameters and evaluate the results before the rest of the tiddler is rendered — so the filter attempts to parse the raw code of the procedure’s definition, not its rendered output.

For really complicated scenarios (e.g. ones involving widgets), $wikify is available. For everything else, in a 5.3.0+ wiki: functions! :slightly_smiling_face:

1 Like

Hmm, wonder when that snuck in. But if I fix that, I still get the recursive transclusion error.

I was floundering, trying many things, hoping one would magically work. I don’t remember exactly how, five weeks ago, I ended up with that attempt. But no, I know of no advantage.

Thank you. This explains the biggest problems I was seeing. I’d really rather never depend on using such a value from the outer context; I’d rather pass the parameters explicitly wherever I need them. But I often feel as though I’m fighting TW when I try to do so. And obviously, sometimes I get it wrong.

This works perfectly! (Once I change my above-title ViewTemplate to match.) Thank you very much!

I had thought Tony was talking about using a function instead of my lineage procedure. That’s actually what I would prefer to do. It would be great if lineage returned the ordered list of ancestor titles that I could then turn into HTML in a crumbs procedure.

I am bothered in this solution in the use of a variable from an outer context instead of a parameter. This may still be an “unlearning” curve, trying to treat TW as I would a functional programming language, but it always bothers me. I just want to pass everything I need, defaulting as necessary. I can’t see whether doing so would be simple, impossible, or somewhere in between.

Some day that will sink in. I want to use functions everywhere I can. But procedures/macros are necessary. And I still don’t have a really good feel of where each can be used. Thank you for the guidance.

Trying to drill this into my head!!!

I think that’s happened to me more than once.


Thank you both very much.

I posted a new version with Emily’s suggested changes at http://scott.sauyet.com/Tiddlywiki/Demo/Breadcrumbs/v2/.

I think it’s a very reasonable impulse, but not one currently well-supported by the parameter syntax. TW prefers to recycle values (variables, procedures, etc.) defined at a higher level, redefining only when a variable requires a different value in an inner context; we see this in the heavy use of global macros/procedures as well. In fact, I suspect that part of the motivation behind the change in macro → procedure syntax (from \define macro(param) <<__param__>> to \procedure proc(param) <<param>>) was specifically to simplify variable inheritance in nested procedures — and if not, then it’s certainly a major benefit. Pre-5.3.0, I had a number of macros with workarounds like $let param=<<__param__>> or $let param=<<param>> just so I could use <<param>> as defined in an outer context. It felt good to be able to remove a lot of specific param declarations and simply reuse the values of existing variables, no matter how they were originally defined.

On the other hand, it seems likely that future versions will offer greater support for dynamic parameters, as Jeremy has hinted here and on GitHub — and that might make it easier to pass default parameters like \procedure proc(param:<<param>>) if you prefer to keep things explicit. This would fill a gap not quite solved by the $parameters widget, which is IMO the current best method for setting a variable as a default parameter to a procedure… but doesn’t offer any equivalent solution for passing parameters to functions.

1 Like

If you want to use a procedure recursively, you need to use parameters, otherwise it will not instantiate a new variable. So you may have really weird side effects.

What I would prefer is to only use parameters. I would be quite happy if even currentTiddler had to be passed around. But I’m quite sure that TW is not likely to move in that direction.