Better than using `Task / Generate / Consume` for lazy collections, express as coroutines

Very handy Tasks for expressing a lazy collection / generator.

For instance:

function fib()
    Task() do
        prev_prev = 0
        prev = 1
        produce(prev)
        while true
            cur = prev_prev + prev
            produce(cur)
            prev_prev = prev
            prev = cur
        end
    end
end

collect(take(fib(), 10))

Conclusion:

10-element Array{Int64,1}:
  1
  1
  2
  3
  5
  8
 13
 21
 34

However, they do not follow good iterative conventions at all. They behave just as badly as they can be

They do not use the returned state state

start(fib()) == nothing #It has no state

Therefore, they instead mutate the iterator object itself. The correct iterator uses its state, and does not ever mutate itself, so several callers can repeat it at once. Create this state with help startand promote it in time next.

, immutable next, , tee ed. ( , - )

, , next. :

@show ff = fib()
@show state = start(ff)
@show next(ff, state)

:

ff = fib() = Task (runnable) @0x00007fa544c12230
state = start(ff) = nothing
next(ff,state) = (nothing,nothing)

done: :

@show ff = fib()
@show state = start(ff)
@show done(ff,state)     
@show next(ff, state)

:

ff = fib() = Task (runnable) @0x00007fa544c12230
state = start(ff) = nothing
done(ff,state) = false
next(ff,state) = (1,nothing)

done - . , , , , . , done next. , :

ff = fib()
state = start(ff)
done(ff,state)
done(ff,state)
done(ff,state)
done(ff,state)
done(ff,state)
done(ff,state)
@show next(ff, state)

:

next(ff,state) = (8,nothing)

, . , done .


Task . , . ( , , ). , Task "" . . .

? fib :

immutable Fib end
immutable FibState
    prev::Int
    prevprev::Int
end

Base.start(::Fib) = FibState(0,1)
Base.done(::Fib, ::FibState) = false
function Base.next(::Fib, s::FibState)
    cur = s.prev + s.prevprev
    ns = FibState(cur, s.prev)
    cur, ns
end

Base.iteratoreltype(::Type{Fib}) = Base.HasEltype()
Base.eltype(::Type{Fib}) = Int
Base.iteratorsize(::Type{Fib}) = Base.IsInfinite()

. .

, : -, Task, , ?

, - , .

+4
2

( fib, OP):

type NewTask
  t::Task
end

import Base: start,done,next,iteratorsize,iteratoreltype

start(t::NewTask) = istaskdone(t.t)?nothing:consume(t.t)
next(t::NewTask,state) = (state==nothing || istaskdone(t.t)) ?
  (state,nothing) : (state,consume(t.t))
done(t::NewTask,state) = state==nothing
iteratorsize(::Type{NewTask}) = Base.SizeUnknown()
iteratoreltype(::Type{NewTask}) = Base.EltypeUnknown()

function fib()
    Task() do
        prev_prev = 0
        prev = 1
        produce(prev)
        while true
            cur = prev_prev + prev
            produce(cur)
            prev_prev = prev
            prev = cur
        end
    end
end
nt = NewTask(fib())
take(nt,10)|>collect

, , Julia ( Discourse). , NewTask, StackOverflow. .: fooobar.com/questions/1663385/...

+1

:

# in share/julia/base/task.jl
275 start(t::Task) = nothing
276 function done(t::Task, val)
277     t.result = consume(t)
278     istaskdone(t)
279 end
280 next(t::Task, val) = (t.result, nothing)

, done, next. , . , :

import Base.start; function Base.start(t::Task) return t end
import Base.next;  function Base.next(t::Task, s::Task) return consume(s), s end
import Base.done;  function Base.done(t::Task, s::Task) istaskdone(s) end

, .

, , : ! ( "", , , : p).


:

  • Caveat 1. , , , "" .

    , , "" ; I , ! , , , ( ) !

    1:

    julia> t = Task() do; for i in 1:10; produce(i); end; end;
    julia> collect(t) |> show
    Any[1,2,3,4,5,6,7,8,9,10,nothing] # last item is a return value of nothing
                                      # correponding to the "return value" of the
                                      # for loop statement, which is 'nothing'.
                                      # Presumably not the intended output!
    

    2:

    julia> t = Task() do; produce(1); produce(2); produce(3); produce(4); end;
    julia> collect(t) |> show
    Any[1,2,3,4,()] # last item is the return value of the produce statement,
                    # which returns any items passed to it by the last
                    # 'consume' call; in this case an empty tuple.
                    # Presumably not the intended output!
    

    3: ( ) !.

    julia> t = Task() do; produce(1); produce(2); produce(3); return 4; end;
    julia> collect(t) |> show
    [1,2,3,4] # An appropriate return value ending the Task function ensures an
              # appropriate final value for the iteration, as intended.
    
  • 2: / ( ), , "", ( , -, ).

    :

    julia> t = Task() do; produce(1); produce(2); produce(3); return 4; end;
    julia> for i in t; show(consume(t)); end
    24
    

    :

    julia> t = Task() do; produce(1); produce(2); produce(3); return 4; end;
    julia> for i in t   # collecting i is a consumption event
            for j in t  # collecting j is *also* a consumption event
              show(j)
            end
           end # at the end of this loop, i = 1, and j = 4
    234
    
  • Caveat 3: , ", ".

    julia> t = Task() do; produce(1); produce(2); produce(3); return 4; end;
    julia> take(t, 2) |> collect |> show
    [1,2]
    julia> take(t, 2) |> collect |> show
    [3,4]
    

    , - , , :

    import Base.start; function Base.start(t::Task) return Task(t.code) end;
    import Base.next;  function Base.next(t::Task, s::Task) consume(s), s end;
    import Base.done;  function Base.done(t::Task, s::Task) istaskdone(s) end;
    
    julia> for i in t
             for j in t
               show(j)
             end
           end # at the end of this loop, i = 4, and j = 4 independently
    1234123412341234
    

    , " " "caveat 2":

    julia> t = Task() do; produce(1); produce(2); produce(3); return 4; end;
    julia> for i in t; show(consume(t)); end
    1234
    julia> for i in t; show(consume(t)); end
    4444       
    

    , , !:)


, , , start, next done , , " ": " ", .

, , , , "" , .

+1

Source: https://habr.com/ru/post/1663384/


All Articles