Hi. Do you know why divide1 does compile and divid...
# arrow
ł
Hi. Do you know why divide1 does compile and divide2 does not?
s
Hmm. I'm actually a bit surprised that
divide1
works. What happens if you try to call
kotlinx.coroutines.delay
from
divide1
? It should not be possible since
EagerEffectScope<*>
doesn't allow foreign suspension.
The difference between
divide1
and
divide2
is that
this
is different due to how context receivers vs extension functions are currently implemented.
this
is not properly recognized, at least not by IDEA hints. (not sure if this will change)
So the compiler can recognize that
suspend + EagerEffectScope<String>
belongs together, and it allows you to call
EagerEffectScope#ensure
which is great! But due to how currently context receivers are implemented it doesn't recognize that the
suspend fun ensure
usage belongs to the context so it's valid.
ł
So we cannot currently mix EagerEffectScope and context receivers. Don't you think it's an issue with context receivers that we should report?
s
Yes, I think it would be good to issue a report for this on YouTrack. I can help you reproduce a smaller example if you want. This is coming from the
RestrictSuspension
annotation on
EagerEffectScope
itself. It seems that it's properly taken into account for extension functions, but not context receivers.
Copy code
@RestrictSuspension
interface Example {
  suspend fun test(): Unit
}

suspend fun Example.works(): Unit = test()

context(Example)
suspend fun doesNotWork(): Unit = test()
I assume it's more tricky for context receivers, because what should happen if we add a second receiver? Then it would no longer be correct due to
RestrictSuspension
.
So it's potentially not a bug but desired, but nevertheless might be good to report.
ł
Could you please take a look?
Copy code
@RestrictSuspension
interface Example {
  suspend fun test(): Unit
  suspend fun works(): Unit = test()
}
What should happen when we add a receiver extension to the 'works' function in the example above? It is similar question to the one you are asking. Question already addressed long before context receivers were introduced.
s
What should happen when we add an extension receiver to the 'works' function in the example above?
Nothing, the
test
invocation inside
work
is correct. Afaik there is no extension receiver you can add to
work
to break the usage of
test
. I think I phrased it a bit incorrectly. And when looking at the documentation it's stated much more clearly.
```Classes and interfaces marked with this annotation are restricted when used as receivers for extension
suspend
functions. These
suspend
extensions can only invoke other member or extension
suspend
functions on this particular
receiver and are restricted from calling arbitrary suspension functions.```
when used as receivers for extension
suspend
functions
, so it only applies when used as receiver extensions. Which is what
EagerEffectScope
does to enable the DSL inside
eagerEffect
, and this is why you could call
ensure
when you had
EagerEffectScope
as the receiver extension. Context receivers are not equivalent to the receiver extension since the receiver extension is semantically the same as a "member", whilst context receivers imply the types are available in the enviroment around us.
I hope that makes more sense and answers your question more clearly. With all that in mind, I think this behavior is correct and doesn't warrant any actions or reports.
ł
The need (of eagerEffect) arose because I'm trying to use Either in kafka streams, where I need to provide a function that maps the input to the output and cannot just call the suspend function. Do you recommend runBlocking for this particular usage? Do you have any other idea how to combine EffectScope, multiple context receivers and kafka streams?
s
Well, I would recommend using
EagerEffectScope
the extension function style in that case. Do you have a significant reason that you want to use it with context receiver instead? I would absolutely not recommend using runBlocking, and I advise to not use it anywhere except
fun main(): Unit = runBlocking {
However, it might be possible to support
suspend
if Kafka Stream has any support for
Future
or
KafkaFuture
(from Kafka SDK)
ł
Now (though still only in arrow-2 branch) it works 🙂 . You hardly imagine how I missed this feature.