Очень удобно использовать Tasks
, чтобы выразить ленивую коллекцию/генератор.Лучше, чем использовать `Задача/производить/потреблять` для ленивых коллекций, выражать как сопрограммы
Например:
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
Однако, они не следуют хорошие конвенции итераторов на всех. Они так плохо вел себя, как они могут быть
Они не используют возвращенное состояние state
start(fib()) == nothing #It has no state
Таким образом, они вместо того, чтобы мутировать сам объект итератора. Правильный итератор использует свое состояние, а не когда-либо мутирует себя, поэтому несколько вызывающих абонентов могут повторять его сразу. Создав это состояние с start
, продвигаясь в next
.
дебаты, умело, что государство должно быть immutable
с next
возвращающей новое состояние, так что может быть тривиальным tee
ред. (С другой стороны, выделение новой памяти - хотя в стеке)
Более того, скрытое состояние, оно не продвинулось во время 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
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()
Но это немного менее интуитивным. Для более сложных функций это гораздо менее приятно.
Так что мой вопрос: Что может быть лучшим способом иметь что-то, что работает как в качестве задачи, как способ создания итератора из одной функции, но это хорошо себя ведет?
Я не удивлюсь, если кто-то уже написал пакет с макросом, чтобы решить эту проблему.