recursionrebolrebol3rebol2red-lang

Breaking out of a recursive function


I'm walking through a set of nested blocks and want to stop the walk when I've found the value I'm looking for.

For reasons that are beyond the scope of this question, I can't use PARSE for this particular problem, nor use FOREACH as the looper:

walk: func [series [block!] criteria [block!]][
    use [value] compose/deep [
        while [not tail? series][
            value: pick series 1

            either block? value [
                walk value criteria
            ][
                (to paren! criteria)
            ]

            series: next series
        ]
    ]
]

I'd like to break out if I find this specific value.

walk [a [b c [d e] f] g] [if value = 'e [return value]]
; returns 'e

However, I'd also like to do operations that don't break out:

walk [a [b c [d e] f] g] [
    collect [if find [c e] value [keep value]]
]
; returns [c e]

Would like to try and solve this for any of the Rebol flavours including Red. Any thoughts as to efficiency (reason I use a block instead of a function), etc. would be welcome too.


Solution

  • The function combo I was looking for is CATCH/THROW. Once again, using the given function:

    walk: func [series [block!] criteria [block!]][
        use [value] compose/deep [
            while [not tail? series][
                value: pick series 1
    
                either block? value [
                    walk value criteria
                ][
                    (to paren! criteria)
                ]
    
                series: next series
            ]
        ]
    ]
    

    I can simply wrap it as follows:

    catch [walk [a [b c [d e] f] g] [if value = 'e [throw value]]]
    ; returns 'e
    

    Some Notes

    I'll just have WALK return NONE (am using ALSO just so as not to leave an awkward trailing none):

     walk: func [series [block!] criteria [block!]][
          also none use [value] compose/deep [
              while [not tail? series][
                  value: pick series 1
    
                  either block? value [
                      walk value criteria
                  ][
                      (to paren! criteria)
                  ]
    
                  series: next series
              ]
          ]
      ]
    

    This introduces a complication as I only want to bind the block to the word VALUE. If I were to rewrite the function as follows:

    walk: func [series [block!] criteria [block!] /local value][
        do bind compose/deep [
            while [not tail? series][
                value: pick series 1
    
                either block? value [
                    walk value criteria
                ][
                    (to paren! criteria)
                ]
    
                series: next series
            ]
        ] 'value
    ]
    

    Then it also binds that same block to the words SERIES and CRITERIA which would override the binding of any such words from the calling context, e.g.:

    walk [some values][series: none probe value] ; results in error