Kevin Hoffman
Kevin Hoffman

Reputation: 5152

Idiomatic Scala - Performing Functions on Tuple Values

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

Answers (1)

Randall Schulz
Randall Schulz

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

Related Questions