Reputation: 5152
Folks, I've got the following map and structural type defined:
type Mergeable = { def mergeFrom(data: Array[Byte]): com.google.protobuf.GeneratedMessageLite }
val dispatchMap = Map(
1 -> (ZombieSighting.defaultInstance.asInstanceOf[Mergeable], "akka://UlyssesAgenda/user/ServerCore/DispatchTarget")
)
Basically what I'm doing is defining a map that says "when I read protobuf message type 1 off the wire, create a ZombieSighting from the bytes, and dispatch that to the actor found at the indicated string".
So, this is the code I have today that creates a message, an actor, and sends the message to the actor:
val dispatchInfo = dispatchMap.get(messageType)
val theMessage = dispatchInfo.map { _._1.mergeFrom(theBytes) }.get
val actorPath = dispatchInfo.map { _._2 }
val targetActor = actorPath.map { SocketServer.system.actorFor(_) }
targetActor.map { _ ! theMessage }
When I look at this, all I can think of is how many lines of non-functional programming this looks like and I can't help but think there's a much more elegant way to do this. I know I can write functions that take tuples as parameters and return modified tuples, but I don't think that buys me anything in terms of clean idiomatic scala here.
My gut instinct says there's a single map statement I can run on "dispatchMap.get(messageType)" that will give me an instance of an Akka actor based on tuple._2 and a merged protobuf object based on tuple._1.
Can someone suggest a way to pimp this code and reduce the number of lines and make it more functional? e.g.
val (targetActor, theMessage) = ????
targetActor ! theMessage
Edit: here's my first attempt at a refactor.. Is this the "Scala way" to do it?
val (msg, actor) = dispatchMap.get(messageType).map {
case (m:Mergeable, path:String) =>
(m.mergeFrom(theBytes), SocketServer.system.actorFor(path) )
}.get
actor ! msg
Edit 2: Here's a version suggested by the commenter below, but I don't know if this is really idiomatic or not:
def dispatchMessage: IO.Iteratee[Unit] =
repeat {
for {
(messageType, byteString) <- readMessage
} yield {
for (
(protoMessage, actorPath) <- dispatchMap.get(messageType);
theMessage = protoMessage.mergeFrom(byteString.toArray);
targetActor = SocketServer.system.actorFor(actorPath) )
yield
targetActor ! theMessage
}
}
?
Upvotes: 0
Views: 252
Reputation: 26486
Your code might be written like this:
val akkaResponseFuture =
for ((bytes, actorPath) <- dispatchMap.get(messageType);
theMessage <- bytes.mergeFrom(theBytes);
targetActor = SocketServer.system.actorFor(actorPath))
yield
targetActor ! theMessage
This may very well not be right. I did it without trying to create working code, since your sample is not self-contained. Also, theBytes
is nowhere defined.
But I'm pretty sure you can use a for comprehension to clarify and streamline it.
Upvotes: 3