Should actor methods run sync per default, when there is no assignment made
When an actor method is run and the return value assigned to a local variable, the execution is synchronous, since it must be synchronous in order to get the return value.
actor Bla():
def foo():
return 42
actor main(env):
b = Bla()
f = b.foo() # synchronous
however, if we just call a method without assigning its return value, the execution is asynchronous. This is surprising to many Python developers. While it is certainly something one might learn, I can't help but wonder if it'd be easier if the default would be to remain with synchronous execution. This would force the use of the async keyword to get asynchronous execution.
Given that actors and classes look pretty much identical and calling methods on classes and actors are virtually inseparaable, like visually in the code, our syntax gives no hint to whether something is an actor or a class. You have to look at the definition of a name in order to know whether it is an actor or class. Class methods always run synchronously, so you simply have to know if we are dealing with an actor.
a.foo() # is it sync or not? well, what is a?
b.foo() # hah, it was async!! fooled you
Having to add async feels like a rather small syntactic thing. Also, I think a lot of the time when we call methods, we assign the return values, so this is a bit of the less common case.
Anyway, just opening this to discuss and collect opinions.
@nordlander let's start with you, how do you feel about this?
One aspect of it is that it's not that easy to tell he difference between synchronous and asynchronous execution, at least not when only a client and a server actor are involved. The reason is that any subsequent query to the server about the effect of the previous call is guaranteed to run after the first call, so the client won't be able to spot the difference. For this to happen a third actor must also be involved, such that A calls B and then C, B calls C as a result of being invoked, and finally A asks C about its state -- only to (occasionally) find out that the first message via B hasn't yet arrived at C. I agree that this can be surprising to newcomer, but I also think it's reasonably easy to adapt to the new execution model.
A nice benefit of the implicitly asynchronous choice is that it enables significant concurrency of an actor system without requiring any explicit concurrency annotations. This is the upside of having to adjust the mental model of what happens when three or more actors communicate along joining paths.
Still, I think the most serious argument in favor of implicit asynchrony is that without it, essentially every communication pattern that involves a cycle would require explicit annotations to avoid deadlock. With implicit asynchrony, pretty much every pattern avoids that problem, and only what I'd call "contrieved" patterns are at risk (which our upcoming static analysis will find!). That's a really important aspect, in my mind.
We want to encourage the async-per-default mental model, which is the current behavior.. so not changing that. Nothing to do here. Closing this.