比使用“任务/生产/消费"来表现为协程的惰性集合更好的方法 [英] Better way than using `Task/produce/consume` for lazy collections express as coroutines

查看:113
本文介绍了比使用“任务/生产/消费"来表现为协程的惰性集合更好的方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

使用Tasks非常方便 表达一个懒惰的集合/一个生成器.

It is very convenient to use Tasks to express a lazy collection / a generator.

例如:

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))

输出:

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

但是,它们根本不遵循良好的迭代器约定. 他们表现得很糟糕

However, they do not follow good iterator conventions at all. They are as badly behaved as they can be

他们不使用返回状态state

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

因此,它们代替了迭代器对象本身. 适当的迭代器使用其状态,而不是对其自身进行任何更改,因此它们的多个调用者可以立即对其进行迭代. 使用start创建该状态,并在next期间进行.

So they are instead mutating the iterator object itself. An proper iterator uses its state, rather than ever mutating itself, so they multiple callers can iterate it at once. Creating that state with start, and advancing it during next.

有争议的是,该状态应为immutable,而next返回一个新状态,因此可以轻松地tee. (另一方面,分配新的内存-尽管在堆栈上)

Debate-ably, that state should be immutable with next returning a new state, so that can be trivially teeed. (On the other hand, allocating new memory -- though on the stack)

此外,它是隐藏状态,在next期间它没有前进. 以下内容不起作用:

Further-more, the hidden state, it not advanced during next. The following does not work:

@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期间处于高级状态: 以下作品:

Instead the hidden state is advanced during done: The following works:

@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之前被调用. 仍然不是很好,因为发生了以下情况:

Advancing state during done isn't the worst thing in the world. After all, it is often the case that it is hard to know when you are done, without going to try and find the next state. One would hope done would always be called before next. Still it is not great, since the following happens:

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可以安全地多次呼叫.

Which is really now what you expect. It is reasonably to assume that done is safe to call multiple times.

基本上Task会使迭代器变差.在许多情况下,它们与需要迭代器的其他代码不兼容. (在很多情况下都是这样,但是很难说出是哪一个). 这是因为在这些生成器"函数中,Task并不是真正用作迭代器.它们旨在用于低级控制流. 并进行了优化.

Basically Tasks make poor iterators. In many cases they are not compatible with other code that expects an iterator. (In many they are, but it is hard to tell which from which). This is because Tasks are not really for use as iterators, in these "generator" functions. They are intended for low-level control flow. And are optimized as such.

那么更好的方法是什么? 为fib编写迭代器还不错:

So what is the better way? Writing an iterator for fib isn't too bad:

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()

但是有点不那么直观. 对于更复杂的功能,它就不太好了.

But is is a bit less intuitive. For more complex functions, it is much less nice.

所以我的问题是: 有什么更好的方法来像Task那样工作,从单个函数构建迭代器,但是行为良好?

如果有人已经编写了一个带有宏的软件包来解决这个问题,我不会感到惊讶.

I would not be surprised if someone has already written a package with a macro to solve this.

推荐答案

以下内容如何(使用OP中定义的fib):

How about the following (uses fib defined in 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问题进行改进的答案.请参阅: https://stackoverflow.com/a/41068765/3580870

This is a good question, and is possibly better suited to the Julia list (now on Discourse platform). In any case, using defined NewTask an improved answer to a recent StackOverflow question is possible. See: https://stackoverflow.com/a/41068765/3580870

这篇关于比使用“任务/生产/消费"来表现为协程的惰性集合更好的方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆