Bidirectional communication (replies)
Opened this issue · 4 comments
Right now Mailbox(M)
merely delegates to Channel(M)
which only permits unidirectional communication from an actor A to an actor B, but doesn't permit B to reply to A after it processed its message, because there is no way to know about A. In order to achieve this the developer must manually create and use a type to wrap the message (e.g. {Actor, M}
) then manually which is bothersome.
Earl must introduce a solution.
Earl relies on direct method calls on actor objects for simpleness. In this case actor.send(msg)
.
We could add an optional argument pass an actor as a second parameter, that is actor.send(msg, self)
, and have Mailbox wrap Channel({Actor, M})
instead of Channel(M)
, but receive methods are affected, too.
-
Shall they return the tuple as-is? That would allow
message, actor = receive
ormessage, _ = receive
patterns which are nice, but will raise an exception when the channel is closed, which shall be silenced (kinda useless) or create noise in logs.The
receive?
method can currently be used to break out of loops swiftly (without raising exceptions) withwhile message = receive?
but that would now require to expand the tuple. This may be an acceptable compromise if the actor is meant to reply, but bothersome otherwise and theactor
variable is merely ignored:while m = receive? message, _ = m # ... end
-
Shall they return the message only, memoizing the actor as
#reply_to
for example? The mailbox would still behaving the same (while message = receive?
) for all use cases, but could choose to reply withreply_to.send(response)
or check whether the sender passed it's reference to receive a reply:if actor = reply_to? actor.send(response) end
-
Shall we introduce alternative receive methods? I'm afraid they'd be quite ugly named (e.g.
receive_with_actor
) or require to change thereceive
methods to return the tuple and introduce#receive_message
alternatives.
Among possibilities, the send paradigm could be changed.
Instead of calling #send
on the destination we'd call #send
on the current actor, passing the destination actor as an argument. That is change destination.send(message)
into send(destination, message)
that would eventually call destination.mailbox.send({message, self})
to pass the sender actor to the receiver transparently.
Aparte: Crystal doesn't implement matchers to deconstruct tuples, so we can't use something like the following as a 4th alternative to the receiver dilemma, it would be as bothersome as the 1st (but with a nice vibe):
loop do
match receive?
when {message, actor}
# ...
else
break
end
end
I don't think Earl will ever fix that. Even in Erlang you must explicitly pass a {M, self()}
, but pattern matching makes it easy to deconstruct (receive {M, Pid} -> Pid ! process(M) end.
)