11 Replies Latest reply on Apr 19, 2012 11:34 AM by Dillik

    Recursive CF help

    BobSchwenkler

      Title

      Recursive CF help

      Post

      Hi, I'm working on my first recursive custom function and not getting too far.

       

      I want to take a return separated list:

      Song 1

      Song 2

      Song 3

       

      and simply add text around each line:

      Text Song 1 More Text

      Text Song 2 More Text

      Text Song 3 More Text

       

      The text is being pulled using the List function through a relationship. There may be multiple ways to do this, my first reaction is to use MiddleValues. I'm having trouble figuring out how to get my incrementing variable that would be needed here, as well as how to insert the recursion. I could post what I've got thus far, but it really isn't much to go on. Thanks in advance!

        • 1. Re: Recursive CF help
          philmodjunk

          I think right values is what you need. The trick is in how you pass the parameters. You pass lists of data as parameters to the function and then each recursive function call passes a shortened version of the same list via RightValues. When you get to an empty list parameter, recursion stops.

          RightValues ( ListParameter ; valueCount ( ListParameter ) - 1 )

          Will pass a list of values with the first value removed.

          • 2. Re: Recursive CF help
            BobSchwenkler

            Took me a long time to get my head wrapped around this type of function. Here's what I ended up with:

             

            One input field "Items" is looking at a related field with multiple
            matches on the far end. The function is called TestFunction

            Let(
            [$Counter=$Counter+1;
            currentItem=Substitute(

            MiddleValues(List(Items);$Counter;1);¶;"")
            ];

            Case($Counter<ValueCount(List(Items))+1;
            "Text " & currentItem & " More Text" & TestFunction(relatedField);
            ))
            • 3. Re: Recursive CF help
              BobSchwenkler

              I have another confusing problem that I'm running into with this function. I have it calculating as the result of an unstored calc field.

               

              When I manually click into the field my Case statement is calculating as fail the first time around. My custom function, as is, returns nothing. The same occurs when I try to export the field contents to CSV (which is what this field is destined to be doing in the future). It also fails when I pull its contents into a scripted variable. However, when I display the field contents in a Show Custom Dialog script step everything appears.

               

              Seemingly extra confusingly, when I store the field contents into a scripted variable and then display that variable in my custom dialog it is also working, even though my data viewer shows that variable's contents as having failed. What gives?

              • 4. Re: Recursive CF help
                philmodjunk

                I'd write it this way:

                // TestFunction ( TheList )
                // TheList a text field containing the list of values to be processed by this function
                //
                If  ( ValueCount ( theList ) = 0 ; "" // recursion is complete
                       "text " & getvalue ( TheList ; 1 ) & " More Text" & TestFunction ( Rightvalues ( TheList ; valueCount ( TheList ) - 1 ) )
                     )

                You'd make your initial call to this function with:

                TestFunction ( List ( RelatedTable::Field ) )

                • 5. Re: Recursive CF help
                  BobSchwenkler

                  That is cleaner, thanks.

                   

                  I figured out my weird problem, it had to do with $Counter not resetting after the function had run. Your function obviates the variable and perhaps I'll use it instead, but I'm still interested to know what the resetting of variables in a CF depends on and how it works so I can anticipate it in the future. Do you know this?

                  • 6. Re: Recursive CF help
                    philmodjunk

                    A $variable retains its value as long as the script that gave it a value continues to run. a $$variable is global and retains a value as long as you have the file open. What happens to a $variable when a custom function modifies it? don't know.

                    I have not had the need to set values to a variable inside a custom function. Such "side effect" programming is an option I use only when I have no reasonable alternative. (Side effect programming is when data is changed as a "side effect" to the main value returned by a function or script. Such effects tend to be easily missed when you review your code at a later date and thus are best avoided when possibile and carefully documented when it is not.

                    • 7. Re: Recursive CF help
                      Dillik

                      I came across this thread after discovering some weirdness relating to the $variable after a custom function uses it.  It seems the custom function never has a chance to release the $variable, and so it becomes persistent (and unrelated to any similarly-named $variables that intervening scripts may use).

                      You can make your custom function clear the $variable when it's all done, though, like so (recursive CF named "Test" designed to print numbers 1 through 5 as a return-separated list):

                      Let ( $Z = $Z+1;
                      $Z & ¶ &
                      If ( $Z < 5; Test; Let ( $Z = ""; "" ) )
                      )

                      A funny thing happens, though, when you use the List function, which I would've thought would be equivalent to above:

                      Let ( $Z = $Z+1;
                      List ( $Z;
                      If ( $Z < 5; Test; Let ( $Z = ""; "" ) )
                      )
                      )

                      Whereas the first example worked, the second one doesn't because the List function (for some reason I don't understand) apparently considers both results of the If() and forces $Z to reset on its first iteration.  This happens to me in both 11 and 12, and it happens whether you use local $variables or global $$variables.

                      This may be peculiar enough to make one want to shy away from using $variables in recursive CFs, but when you can't carry variables across iterations, you either have those wacky "leaveThisParameterBlank" parameters, or you have to do some awkward contortions to get the data processed in the right order (and it seems not all functions can be contorted as such).  Persistent variables are useful, bugs notwithstanding.

                      Er, but then again, I guess we have no guarantee that this undocumented use for $variables in recursive CFs will ever get less buggy (it could even be removed at some point!).

                      • 8. Re: Recursive CF help
                        philmodjunk

                        I can almost always avoid using a $variable because I pass such values as parameters defined as part of the funciton call. I don't quite picture what you mean by "wacky leave this parameter blank". These parameters would definitely not be left blank.

                        • 9. Re: Recursive CF help
                          Dillik

                          Well, maybe there's a more artful solution in some (most?) cases, but I've both seen and written functions that (barring the use of $variables) need a superfluous parameter to serve as a working variable; it only becomes meaningful as the function recurses, but the user had no reason to have to put anything in there when they first called it.  Right now, though, I'm only able to imagine examples that start out with numeric initialization rather than blank-string initialization (blank-string being what I called "leaveThisParameterBlank").

                          For instance, let's say you wanted a recursive function that listed all values in a particular field across the found set.  Clearly the function needs a parameter to determine which field to check, and that's probably all the function's user is interested in specifying.  We function writers, though, are left to figure out how the function will keep track of its progress through the found set if it doesn't use a $variable.  The standard approach I've seen is to make the function have a startWithRecordNumber parameter which ticks forward with each recursion.  That works fine, but the function now has two parameters, one of which was put there only from a technical necessity, not because it's an important feature to be able to specify which record to start on.  (We can claim it's a feature in retrospect, but in all honesty, we just put it there because we had to; it wasn't part of the function's original purpose.)

                          Another example: a ListArray function that accepts two return-separated lists and produces all combinations thereof, where ListArray("A¶B";"C¶D") produces AC¶AD¶BC¶BD.  I'm not a genius at recursive functions, but as far as I'm able to determine, this function needs to keep track of which row and column it's on (using rows/columns as an analogy for the two lists being combined).  In this case, I think the temptation would be to include two extra parameters, requiring a starting call of ListArray("A¶B";"C¶D";1;1).  Again, that's not terrible, but we can't honestly say we were really itching for a function that combined two lists AND would let us specify a row and column to start with.  Two parameters for this function is all the function's user should have reason to care about; anything else just requires extra thinking time and may be confusing.  (The two-parameter version works great with $variables that are cleared at the end.)

                          • 10. Re: Recursive CF help
                            philmodjunk

                            Ok, I get what you are saying, but even in other programming languages that support recursive function calls, such a parameter is pretty much SOP. I just don't see it as "wacky", rather it's a necessary part of implementing the recursive call. It's not useless as it enables the function to work without side effects--a much more undesirable issue in my book--and this value typically is the value needed to determine when recursion stops stacking up values in the call stack and starts popping them off to return values back up the stack to produce the ultimate value needed for the original call of the function. (In simpler terms, when the parameter is empty or zero, the function stops making recursive calls and returns a simple value as the answer.)

                            • 11. Re: Recursive CF help
                              Dillik

                              Fair enough, although I don't spend a lot of time thinking about other languages' requirements when I try to find a good FileMaker solution.  Personally, though, I expect to spend more time using the completed function than I spend writing it, so if I can make a function be more intuitive when I go to implement it (maybe even X months down the road), I'd rather do that even if it means contorting the function code a wee bit to dodge $variable bugs.  Namely, you need to reset your $variables at the end and be mindful of certain functions that can oddly trigger a premature reset.  It's one-time unpleasant pre-work for unlimited pleasurable usage afterward.

                              But certainly, one would be using $variables at their own risk.  Perhaps global $$variables would be more prudent, because while FMI could later remove all persistence of $variables outside of scripts, I don't expect anything to happen to global $$variables.