scalaintellij-ideaakkaactorcode-navigation

Navigating the code of an actor system in Intellij IDEA


I use IntelliJ IDEA, but the question could relate to other IDEs. There is a great way to navigate the code with Ctrl+click. From the method call it jumps to the method declaration. It really boosts the productivity.

Actor systems are based on message passing. Example in Akka with Scala:

class MyMessage
object MyMessage

class MyActor1 extends Actor {
  context.actorOf(Props[MyActor2]) ! MyMessage
}

class MyActor2 extends Actor {
  def receive = {
    case MyMessage =>
      ...
  }
}

Is there a way to navigate in code between sending the message and receiving the message?

I mean clicking on ! will take me to the definition of ! method in ScalaActorRef, but that's 99% chance that I don't want that. Jumping to the corresponding receive method (or, if possible, to correct case: case MyMessage) would be more appropriate.

How do you navigate the code between actors?


Solution

  • I don't think it is possible in general because an actor can change its behavior at runtime, including what messages it can process - as opposed to methods which can be statically indexed. For example, receive function may be computed depending on the actor state:

    class MyActor extends Actor {
      var i = 0
    
      def receive = firstReceive
    
      def commonReceive = {
        case Increment =>
          i += 1
          if (i % 3 == 0) context.become(firstReceive)
          else context.become(secondReceive)
      }
    
      def firstReceive = commonReceive orElse {
        case Ping =>
          sender ! "zero"
      }
    
      def secondReceive = commonReceive orElse {
        case Ping =>
          sender ! "one or two"
      }
    }
    

    Now the actor handles messages differently depending on which messages it handled before. And this is only a simple example - actual actor behavior may even be received from the outside!

    case class Behavior(receive: Actor.Receive)
    
    class MyActor extends Actor {
      def receive = {
        case Behavior(r) => context.become(r)
      }
    }
    

    Another difficulty which is even greater is that you usually have an ActorRef to which you send messages with !. This ActorRef has no static connection with the actor class which contains message handling logic - it is instantiated with Props which can use arbitrary code to determine which actor class should be used:

    val r = new Random
    val a = actorSystem.actorOf(Props(if (r.nextInt(100) > 50) new FirstActor else new SecondActor))
    a ! Message  // which handler should this declaration lead to?
    

    This makes finding actual message handler next to impossible.

    If you think that it may be worth it to support simpler cases, like the one you provided, you can always submit a feature request to YouTrack.