I’ve been fiddling around with iteration of a tree traversal to learn about interfaces and channels, and I’ve run into something I can’t seem to get around.
When using an unbuffered Channel()
, iteration in a for loop blocks after the first value while using general functions (e.g. push!
, collect
, comprehensions, etc), however, it continues to completion when using I/O (e.g. print
, @show
, etc). This is the first big distinction I’ve found, but I accept there very well may be more subtlety to the matter (I did notice that Base.take!
is also defined for I/O).
MWE (most relevant code at top):
abstract type AbstractNode end
function traverse(node::AbstractNode, c::Channel)
put!(c, node.data)
foreach(branch -> traverse(branch, c), branches(node))
end
function Base.iterate(node::AbstractNode)
state = Channel(c -> traverse(node, c))
isready(state) ? (take!(state), state) : nothing
end
function Base.iterate(node::AbstractNode, state)
#println(isready(state))
isready(state) ? (take!(state), state) : nothing
end
Base.iterate(::AbstractNode, ::Nothing) = nothing
Base.IteratorSize(::AbstractNode) = Base.SizeUnknown()
struct Leaf <: AbstractNode
data::Int
end
struct Node <: AbstractNode
data::Int
leftbranch::Union{Node, Leaf}
rightbranch::Union{Node, Leaf}
end
struct Root <: AbstractNode
data::Int
leftbranch::Union{Node, Leaf}
rightbranch::Union{Node, Leaf}
end
data(node::Union{Root, Node, Leaf}) = node.data
branches(node::Union{Node, Root}) = (node.leftbranch, node.rightbranch)
branches(node::Leaf) = ()
With the following examples:
julia> tree = Root(5, Node(4, Leaf(3), Leaf(2)), Node(6, Leaf(7), Leaf(8)));
julia> [n for n in tree]
1-element Vector{Any}:
5
julia> foreach(n -> println(n), tree)
5
4
3
2
6
7
8
julia> [n for n in Channel(c -> traverse(tree, c))]
7-element Vector{Int64}:
5
4
3
2
6
7
8
Also, uncommenting the print(isready(state))
line in my Base.iterate(node::AbstractNode, state)
method allows for full iteration (note: printing is necessary and pushing isready(state)
to an array does not work). It appears the call to take!
doesn’t unblock the channel as I would have expected.
The following code (modified from Interfaces
in the Manual) returns the full array, with the Channel
being unblocked by the call to push!
:
arr = []
next = iterate(tree)
while next !== nothing
(item, state) = next
push!(arr, item)
next = iterate(tree, state)
end
arr
The example in the manual says that this is what for item in itr push!(arr, item) end
translates into, so I’m not sure why this while loop works, but the for loop fails.
Note: I’m interested in figuring out why this is happening for pedagogical reasons and would like to see if it is possible to find a fix that still uses an unbuffered channel.
Other work-arounds I’ve already found include:
- forgoing the
Channel
and simply pushing thedata
from thetraverse
function to an array - similarly,
collect
theChannel
and usepopfirst!
instead oftake!
- using a buffered
Channel(c -> traverse(node, c), n)
wheren ≥ 7
for the provided exampletree
That said, if there is an established “best practice” for this type of example, I’d be happy to know about it as well. Cheers!