使用起来非常方便Tasks
表达一个惰性集合/生成器。
Eg:
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))
Output:
10-element Array{Int64,1}:
1
1
2
3
5
8
13
21
34
然而,它们根本不遵循良好的迭代器约定。
他们的行为极其恶劣
他们不使用返回的状态state
start(fib()) == nothing #It has no state
因此,他们正在改变迭代器对象本身。
正确的迭代器使用其状态,而不是改变自身,因此多个调用者可以立即迭代它。
创建该状态start
,并在期间推进它next
.
有争议的是,该状态应该是immutable
with next
返回一个新的状态,这样就可以很简单tee
编辑。 (另一方面,分配新内存——尽管在堆栈上)
此外,隐藏状态在next
。
以下不起作用:
@show ff = fib()
@show state = start(ff)
@show next(ff, state)
Output:
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)
Output:
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)
Output:
next(ff,state) = (8,nothing)
现在这正是您所期望的。可以合理地假设done
多次拨打是安全的。
基本上Task
s 的迭代器很差。在许多情况下,它们与需要迭代器的其他代码不兼容。 (在很多情况下都是如此,但很难区分哪个)。
这是因为Task
在这些“生成器”函数中,s 并不是真正用作迭代器。它们用于低级控制流。
并进行了这样的优化。
那么更好的方法是什么?
为以下内容编写迭代器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 一样工作,作为从单个函数构建迭代器的方法,但表现良好?
如果有人已经编写了一个带有宏的包来解决这个问题,我不会感到惊讶。