I'm trying to use yield in a sequence to lazily emit items from a visitor but I'm getting the error "Suspension functions can be called only within coroutine body".
I'm not trying to suspend here, just emit an item:
private fun flatten(x: MyType): Sequence<MyType> {
return sequence {
x.accept(object : MyType.Visitor {
override fun visit(a: MyType.SubTypeA) {
[email protected](a)
}
override fun visit(b: MyType.SubTypeB) {
[email protected](b)
}
override fun visit(c: MyType.SubTypeC) {
[email protected](c)
}
})
}
}
I suspected the closure was confusing the compiler so I added this@sequence but it didn't help. What am I doing wrong?
You are. The
yieldmethod needs to suspend to make the sequence lazy. If it didn't suspend,acceptwill visit everything and yield everything immediately, when you try to consume the first element of the sequence.When you try to get an element of the sequence, the code in the
sequencelambda runs, untilyieldgets called. It suspends the execution of the lambda, and gives you back the element you want. The lambda doesn't continue running, until you try to get the next element.So if your
MyType.Visitorcan't suspend, you can't do this lazily. Use something likebuildListinstead.Not only do the visitor methods need to suspend, they also must be extensions on
SequenceScope. This is becausesequenceuses restricted suspension. The idea is that when suspending in asequencelambda, you must keep passing theSequenceScopealong, to keep track of the sequence you are yielding.As a result, all the
visitmethods, as well as theacceptmethod, need to be suspending, and need to be extensions ofSequenceScope<MyType>.The
visitmethods would be declared like:In
sequence, you would need to wrapxwith awith:If you really want to go down this route, you should probably add a new
SuspendVisitor, andacceptSuspend, instead of modifying the existing methods, so that this can still be used in a non-suspending way.