Storing the result of a procedure call in a variable

I want to call a procedure passing variable data and store the result in a new variable. I tried a number of things. Some worked, some didn’t. Here are my findings:

  1. We cannot call the transclusion widget inside a <$let> call:

    <!-- does NOT work -->
    <$let result = <$transclude $variable="myproc" param1=<<var1>> param2=<<var2>> /> >
      <<result>>
    </$let>
    

    It seems (unsurprisingly) that the /> for the <$transclude ends the <$let> abruptly.

  2. We cannot pass parameters to the procedure as filter parameters:

    <!-- does NOT work -->
    <$let result = {{{ [<myproc><var1>,<var2>] }}} >
      <<result>>
    </$let>
    

    Procedure Calls does say

    Procedure calls can be used in filters. The text is not wikified which again means that the parameters will be ignored.

    so I guess this makes sense, although I don’t really understand it what wikification has to do with it.

  3. But we can set the parameters in an outer environment

    <!-- DOES work -->
    <$let 
      param1 = <<var1>>
      param2 = <<var2>>
      result = {{{ [<myproc>] }}} 
    >
     <<result>>
    </$let>
    

    where param1 and param2 are the names of the parameters to myproc.

  4. But there is a subtlety. Default parameters are not defaulted. You need to supply values explicitly:

    \procedure myproc(param1, param2, param3:"XYZ: ")
      \whitespace trim
      <$text text={{{ [<param1>add<param2>addprefix<param3>] }}} />
    \end
    
    <!-- DOES work, the default prefix, `param3` is supplied: -->
    ''test1'': "<<myproc 10 32>>"
    
    
    <!-- does NOT work: default parameter not defaulted -->
    
    <!-- Yes, I know it's silly to set var1/2 and then use them only to set param1/2. -->
    <!--    Imagine that var1/2 are set in some enclosing scope --> 
    <$let 
      var1 = 10
      var2 = 32
      param1 = <<var1>>
      param2 = <<var2>>
      result = {{{ [<myproc>] }}} 
    >
      ''test2'': "<<result>>"
    </$let>
    
    <!-- DOES work - but only because we supply a value for what should have been defaulted  -->
    <$let 
      var1 = 10
      var2 = 32
      param1 = <<var1>>
      param2 = <<var2>>
      param3 = "PDQ: "
      result = {{{ [<myproc>] }}} 
    >
      ''test3'': "<<result>>"
    </$let>
    

    yields

    test1: “XYZ: 42”
    test2: “42”
    test3: "PDQ: 42"0

    Note that param3 is not defaulted or overridden in test2.


Are there other techniques that work for this, especially any that allow default parameters to work properly?

1 Like

This is precisely the use-case for the $wikify widget:

<$wikify name=result text="<$transclude $variable=myproc param1=<<var1>> param2=<<var2>>/>" >
  <<result>>
</$wikify>

Notes:

  • $wikify is like $set, except the text param is fully parsed. The default handling is to capture the plain text output of the code. See https://tiddlywiki.com/#WikifyWidget for other output options.
  • Take care when using double quotes within the text="..." param value. Remember that quotes don’t nest.
  • If your text contains double quotes, you can use single quotes around the text='...' param.
  • If your text contains both single and double quotes, you can put the text in a separate \define and then reference that as a variable, like this:
\define mycode() <$transclude $variable=myproc param1=<<var1>> param2="It's got both quotes"/>
<$wikify name=result text=<<mycode>>><<result>></$wikify>

enjoy,
-e

2 Likes

Ahh, thank you! I’ve used $wikify on various occasions, but all of them had to do with the final HTML output of something that ended up in wikitext. Usually I ended up finding a better technique, and I thought of $wikify the way a friend describes vise-grips: “not the right tool for any job.”

Now I finally see the sorts of jobs this is the right tool for. Thank you.

One follow-up question, if you have the time. I was trying to use this in a long $let widget, where later assignments depend on certain of the earlier ones (for example, b uses a in its definition, and d uses a and c, etc.):

<$let
  a = ...
  b = ...
  c = ...
  d = ...
  param1 = <<b>>
  param2 = <<d>>
  param3 = ... (repeat myproc's default, or override)
  e = {{{ [<myproc>] }}}
  f = ...
  g = ...
>
  <<g>>
</$let>

I definitely prefer this $wikify technique, especially because this will involve a recursive call, and I hate redefining param1/2 as I was planning on doing. But I will not be able to encase it in that $let widget, right? Will I need to do something like

<$let
  a = ...
  b = ...
  c = ...
  d = ...
>
  <$wikify name="e" text="<$transclude $variable='myproc' param1=<<b>> param2=<<d>> />" >
    <$let 
      f = ...
      g = ...
    >
      <<g>>
    </$let>
  </$wikify>
</$let>

Or is there something simpler?

I think this is possible, you can pass the $transclusion as a string. For example

<!-- does WORK -->
\procedure myproc(param1, param2)
This is <<param1>> and this is <<param2>>
\end
\define var1() 110
\define var2() 200
<!-- does NOT work -->
<$let result = '<$transclude $variable="myproc" param1=<<var1>> param2=<<var2>> />' >
  <<result>>
</$let>

Will result is: This is 110 and this is 200

Of course you cannot use it as a wikified value in other operation or calculation, it is a string.

Hi @Scott_Sauyet there is a draft PR for a “wikify” operator. I have severe reservations that are discussed on the PR, but it would allow you to continue to use the chained let assignment technique. There aren’t any docs at the moment, but the tests at the end of the file listing should give you an idea of how it works.

I consider this be a bug, and if TW use those params intentionally, it is confusing. The myprog procedure has param1 and param2, so it should use the same names in its scope, the scope of these parameters is the procedure body.
In contrast, I would expect procedure myproc to be as follows if it uses param1 and param2 variables:

\procedure myproc(param3:"XYZ: ")
  \whitespace trim
  <$text text={{{ [<param1>add<param2>addprefix<param3>] }}} />
\end

Then it is meaningful to use param1 and param2 defined in $let and procedure call happens inside $let.

My second post gives some additional context for my actual needs, and, while I might be able to make that work, Eric’s suggestion of $wikify is probably simpler for my case, even if that means nesting $let > $wikify > $let.

Yes, there are clearly good reasons for reservations. This has much the feel of eval, and we all know the mantra: “eval is evil”. But (and there’s always a but) there are times where it’s essential. In my day job, I work on a JS rules engine. There’s a code-generation subsystem which takes our rules and turns them into a Node module; that can work with simple string manipulation. But there’s also a browser component for creating, editing, and testing the rules. There is no alternative there to using the Function constructor, a form of eval.

Getting an exception granted for that was simple enough. But Tiddlywiki has different concerns. I agree that there would be real possibilities for intermediate users (like me) stumbling across this and abusing it.

But today I would really love to have it available!

One of the things that has bothered me from the beginning is how Tiddlywiki allows action at a distance for procedures, functions, and macros; I would prefer that all local variables passed to an execution had to be passed explicitly. But that’s not how TW works, and I think the behavior here is consistent with the rest of TW. I think of it as a list of nested contexts. The line test2 = <<result>> operates in a context where var1, var2, param1, param2, and result are available. When we call {{{ [<myproc>] }}}, var1, var1, param1 and param2 are already in scope. When we get into the procedure call, we introduce a new scope for the parameters, but if they’re not defined, we just look up to the enclosing scope.

So I don’t think of this as an error. But there is still a bit of “spooky action at a distance.”

This, when it was pointed out to me, totally freaked me out – see Using the Set Widget to Create Global Variables at the bottom of https://tiddlywiki.com/#SetWidget.

Much like you, I think of TW’s declaratives as “scoped” – I’ve also used your (what I call) “ambient vars” (<$let>) to create the scope I need around a call. But that suggested use of <$set> shatters my comfort zone every time I remind myself of it.

Should point out, if you try the example code on TW .com, you'll see it doesn't actually work 100% -- the nested var doesn't survive.

Sure, and then you had to go and share it with me, so I could freak out too? Thanks, buddy!

:slight_smile: