JuliaDynamics / ResumableFunctions.jl

C# style generators a.k.a. semi-coroutines for Julia.
Other
158 stars 19 forks source link

Recursive resumable functions vs PyGen vs Python generator #2

Closed mohamed82008 closed 7 years ago

mohamed82008 commented 7 years ago

Hi Ben,

I will copy and paste the code from my comment in this link. It seems that recursive ResumableFunctions behave differently from PyGen and Python's generator functions in the recursive case, not sure about C#. This is shown in the comparison below:

ResumableFunctions:

a = [[1,2,3],[4,5,6]]
@resumable function g(x)
    if isa(x,Number)
        @yield x
    else
        for i in x
            for j in g(i)
                @yield j
            end
        end
    end
end

for i in g(a)
    println(i)
end

#=
1
false
2
false
3
false
nothing
4
false
5
false
6
false
nothing
nothing
=#

PyGen:

a = [[1,2,3],[4,5,6]]
@pygen function f(x)
    if isa(x,Number)
        yield(x)
    else
        for i in x
            for j in f(i)
                yield(j)
            end
        end
    end
end

for i in f(a)
    println(i)
end

#=
1
2
3
4
5
6
=#

Python:

import numbers
a = [[1,2,3],[4,5,6]]
def f(x):
    if isinstance(x, numbers.Number):
        yield x
    else:
        for i in x:
            for j in f(i):
                yield j

for i in f(a):
    print i

"""
1
2
3
4
5
6
"""
BenLauwens commented 7 years ago

Hi

With 3 minor modifications I can reproduce what you want:

using ResumableFunctions

@resumable function g(x)
    if isa(x,Number)
        return x # If x is a number the @resumable function is only called once
    else
        for i in x
            for j in g(i)
                j == nothing || @yield j # No explicit return is defined, so we have to eliminate the default returns
            end
        end
    end
end

a = [[1,2,3],[4,5,6]]

for i in g(a)
    i==nothing || println(i) # Eliminate the default return
end

A @resumable function will stop when a return is encountered or when the function hits the final end statement. This is also the default behaviour in C# and is a logical consequence of the finite-state-machine implementation using macros. The magic in Python is possible because the compiler can remove the default returns. In PyGen it is also the compiler that handles the tasks, the context switching and the channels which are more expensive than simple function calls as used in ResumableFunctions. FYI PyGen for Julia v0.5 base on the deprecated produce and consume functions has the same problem with your example. To eliminate the default nothing return you have to return explicitly the last value:

using ResumableFunctions

@resumable function fibonnaci(n::Int)
  a = 0
  b = 1
  for i in 1:n-1
    @yield a
    a, b = b, a+b
  end
  a
end

for fib in fibonnaci(10)
  println(fib)
end