Is there a suspending version of the Iterator inte...
# getting-started
j
Is there a suspending version of the Iterator interface? I.e. the same but next/hasNext being suspending?
s
ChannelIterator
has suspending
hasNext
(but non-suspending
next
)
j
Thanks. But nothing like the the AbstractIterface helepr class? Maybe it's just not a common use-case. I basically have tree structure that I want to walk, lazily loading the parents as they are requested. It's a pretty limited use-case so it doesn't have to be an iterator-per se, just thought it made sense 😉
s
Kotlin’s
for/in
is based on operator function conventions, not interfaces. So anything which has
operator fun next
and
operator fun hasNext
is an iterator, and anything that has
operator fun iterator
is iterable
And you’re free to make any or all of those functions
suspend
Unfortunately that doesn’t extend to being able to use all the iterable extension functions on it, though 😞
j
Thanks!
s
Maybe you just need a
Flow
, though
Sounds like it could be a good fit for your use case
j
I was thinking about that, but not sure how well that fits with lazily requesting items one at a time?
s
Flows (by default) are pull-based, so the flow will only produce an element when the consumer asks for it
j
Yeah, but I basically want
Copy code
while (tree.hasNext()) {
  val node = tree.next()
  // do more supending stuff or break
}
Couldn't really figure out how that looks with flows
s
Maybe something like this?
Copy code
flow {
    while(tree.hasNext()) emit(tree.next())
}.takeWhile { node ->
    // do more suspending stuff or return false
}.collect()
j
does the takeWhile "request" one element at a time? e.g. does the while suspend after the first emit?
s
The behaviour of that flow example is actually exactly equivalent to your loop version. A flow is just a way of writing the same code in a different way (basically decoupling the loop condition from the loop body)
so yes, the loop waits for the next bit to run 👍
j
This is not what Jacob means I think, the values are produced without waiting for the consumer approval
j
Right. I like the idea of my "TreeWalker" to basically expose a "flow" of nodes. The extra
collect()
is slightly ugly though, since I'm not really collecting anything and I only want the "onEach" behavior.
@Johann Pardanaud, yeah, that's what I'm asking. I don't want to traverse the tree unless asked for (it will require network traffic etc.)
j
Unfortunately I don’t have a full answer but maybe there is some kind of solution with
Sequence
and
yield
: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence-scope/yield.html
Seems to be what you need
c
Flow
is essentially the same as
Sequence
with a few bonus features (
suspend
,
onCompletion
/`onStart` /…)
👍 1
today i learned 1
j
The sequence builders doesn't seem to play well with running other suspended code or launch other coroutines. Trying something like the above example errors with "Restricted suspending functions can only invoke member or extension suspending functions on their restricted coroutine scope" https://stackoverflow.com/a/49264420 seems to sugest this is by design
c
The
suspend
in Sequence is not a "real"
suspend
. You need to use Flow from KotlinX.Coroutines
j
But I'm back at thinking I don't really need anything special for my use-case. My current code works with just asking for one element at a time and doing the control flow manually. Initially I was going for something similar like the HN post above, to make it possible to just traverse the datastructure in for/in kind of way (but sam answered that before). Comparing with the pagination-example in the HN post. Do you all think that's best modeled as a flow? And instead of using for/in, relying on flow's `takeWhile`/`onEach` etc?
c
I'm not sure it's the only way to do it, but
Sequence
is essentially a rewrite of
Iterable
,
Flow
is sequence +
suspend
, and you said you wanted an iterator with
suspend
.
j
Sometimes I need to be careful with what I think I want 😅 . For some more context I want to have a "TreeWalker" that understands how to traverse a tree in various ways. I need to initialize it with a given node, and then I'd like to have something like:
walker.ancestors()
returning an iterator-esque thing that can be used like an iterator typically would. The above would only be used in suspending methods and the work to traverse the tree is also suspending. One option seems to be to return anything that has a `suspend next()`and
suspend hasNext()
(no help from std lib to implement that - cmp. AbstractIterator with
computeNext()
) The other option would be to have
ancestors()
return a "Flow of ancestors". To consume that I would need to use something like
ancestors().takeWhile { // process or return null to stop traverseal }.collect()
(still not sure I understand if it was possible to emit items lazily, one at a time, in the flow example above) I'm not sure what I want, but it's really interesting to learn the options. Whatever would be most idiomatic is probably preferable ;)
c
Iterator/Sequence/Flow are all flat structures, so by using them you lose your tree information. They're a good fit if you want to do a depth-first search or breadth-first search (or any other search), but you can't use their
.map
(once it's flattened, you can't transform it back into a tree using the stdlib). If you want to keep the structure, you'll have to create your own
map
etc implementation.
109 Views