Best Way to Break out of Recursive Loop in Julia

This is probably not a great example of recursion but suppose I have a recursive function, usually in a loop I can break out of the first layer with something simple like

function check(stuff)
    cont = Ref(true)
    @async for i = stuff
        cont[]||break 
        iseven(i) ? println(i) : sleep(8)
    end
    return cont
end

julia> v = [1,2,3,4,1,2,3,4] 
8-element Vector{Int64}:
 1
 2
 3
 4
 1
 2
 3
 4

julia> test = check(v)
Base.RefValue{Bool}(true)

julia> 2
julia> test[]=false
false

but if the function contains a recursive loop

function reccheck(stuff)
    cont = Ref(true)
    @async for i = stuff
        cont[]||break 
        iseven(i) ? println(i) : reccheck(filter!(x-> x!=i,stuff))
        sleep(8)
    end
    return cont
end   

then the inner loops are no longer stopped by the initial cont [] || break. Just wondering if there was a simple workaround for this? Thanks!

I think it has not much to do with recursion, but rather that you should pass forward the reference to the nestled instances, e.g.

function reccheck(stuff, cont = Ref(true) )
    @async for i = stuff
        cont[] || break
        iseven(i) ? println(i) : reccheck( filter!(x-> x!=i,stuff), cont)
        sleep(8)
    end
    return cont
end   
1 Like

Wow this is super helpful thanks so much! I had no idea this could be done. Could you confirm the following few points to see if my understanding of this process is correct? (Sorry I am still trying to understand on how Ref objects work). According to the docs a Ref object is loaded with []. So if

Instance = recheck(stuff, cont)
  1. By setting Instance[] = false we are loading the reference object in the instance and setting it to false. This in turn trips the cont[] || break circuit. Does this mean Instance[] === cont[]? If cont is only returned after the loop is completed how is Instance reassigning cont prior to the completion of the loop?

  2. The exclusive or circuit in the function had to be cont[] || break and NOT cont || break because without the [] call the reference object would not be loaded?

  3. If there were more than one reference object being returned by the function would the appropriate way to load with separate values be via tuple? e.g.

function reccheck2(stuff, cont = Ref(true), rep = Ref(true))
             # some function 
             return (; cont, rep)
end

Instance2 = recheck2(stuff)
Instance2[1][] = false
Instance2[2][] = true 

I understand if you’re busy and don’t have time for the follow ups, either way thanks so much for the solution!

Hi, I think all your points seems correct.

  1. Since you use @async, cont will be returned before the loop ended it’s computation.
    If you remove the @async all of that would not work anymore.

  2. Yes.

  3. Your approach works.
    Another common design is, to ask the user to create the Ref objects and pass them when calling the function. That way one can use the return for actually meaningfull things.

Just some example, maybe it doesn’t run but to show the idea :wink: Here the user can decide if they want to pass a reference or not, and only the outer execution is async:

function reccheck2(stuff; cont = Ref(true), rep = Ref(true))
   j = 0
   for i = stuff
          cont[]||break 
          iseven(i) ? println(i) : reccheck(filter!(x-> x!=i,stuff), cont = rep, rep = rep)
          sleep(8)
          j += 1
   end
   return j 
end

rep = Ref(true)
@async result = revcheck( 1:8; rep = rep )
rep[] = false
1 Like

ahh…got it. Thanks so much!