That's easily addressable by proper structuring accesses to the property. Plus we have Mutex
now.
Imagine actor in current version, without async setters as a box (mailbox? heh). Inside that box lies the state. You can take a look — like maybe this box is transparent – on what's inside that box. But we can't change what's inside by ourselves — instead we put a "request" to make a modification, that will be processed by the box.
Async setters change this perception, as we can now take something out of this box by ourselves, play with it in any way, and put it back. Box only now can guard that we only one who play with the item, but in general it becomes kinda meaningless, as overseeing can be done by anything, so why to use the box?
Actors propose different outlook on the state in the app. Instead of scattered across various types and domains, it is now isolated, and only in this isolation it gets changed.
Don't you think that this only proves point that async setters are bad? Exactly because you already can have such behaivour with getters. Just with the difference that getters in overall will bring a bit less harm potentially, while still can give undesired behaviour.
And you further prove several points as well with this example
by isolating action to the actor's computed property. You've just improved code by isolating on actor, so why we should promote bad practices introducing async setters?
We still have unresolved question how async setters will behave here
await other.balance = await other.balance + amount
or here
await other.balance = other.balance + amount
(note that both these cases cannot be mitigated by some computed property, because amount
is external to actor state)
or even with your example of computed property
actor ATM {
// ...
var withdrawAmount: Int?
}
let atm = ATM()
await atm.withdrawAmount = bank.totalBalance
In which way any of these cases would be resolved? How clear it would be that there are several suspension points involved?
Compared to (not perfect) isolating this action:
extension BankActor {
func withdrawAll(from atm: ATM) {
await atm.request(amount: totalBalance)
}
}
(note that this probably also a bit wrong from design point, but isolation makes me think about how better communicate the action, not just use async setter and don't think about it)
That's still orthogonal to isolation. Yes, you might be better hiding mainAccount
and savingsAccount
, but not necessarily hiding them makes sense, while isolating does.