Call Graph and Neighbourhood

Call Graph reads who calls a function or method and what it calls in turn; Neighbourhood reads a construct's own structural neighbours - what it extends, implements, composes or references - radiating out by hop. Here deliverAll() (delivery.ek9) calls describeNotification() and the dispatcher route(), both declared in notifications.ek9 - so the call graph crosses the project's two files. To play it in the IDE: Help > Lessons > Understanding a codebase…, or ek9 -ide -lesson call-graph.

The EK9 IDE as the lesson opens

Start from

The code the lesson starts from: copy it into a file of your own and follow the steps.

notifications.ek9

#!ek9
<!-
  The shared project for the 'type-hierarchy' and 'call-graph' Analyse lessons: a small abstract
  hierarchy (Notification), a trait one subtype implements (Loggable), a dispatcher
  (NotificationRouter) with handlers for two of the three subtypes - the third falls back to the
  entry point's own body - and a function that reads any Notification polymorphically, so Type
  Hierarchy, Dispatcher, Call Graph and Neighbourhood each have something real to show.
-!>
defines module lesson.relationships

  defines trait

    Loggable
      logTag() as pure
        <- rtn as String: "LOG"

  defines class

    Notification as abstract
      summary() as abstract
        <- rtn as String?

      default operator ?

    EmailNotification extends Notification with trait of Loggable

      override summary()
        <- rtn as String: `${logTag()}: emailing the customer`

      default operator ?

    SmsNotification extends Notification

      override summary()
        <- rtn as String: "texting the customer"

      default operator ?

    PushNotification extends Notification

      override summary()
        <- rtn as String: "pushing to the app"

      default operator ?

    NotificationRouter

      route() as dispatcher
        -> n as Notification
        <- rtn as String: "unrouted"

      private route()
        -> n as EmailNotification
        <- rtn as String: `sending by email: ${n.summary()}`

      private route()
        -> n as SmsNotification
        <- rtn as String: `sending by sms: ${n.summary()}`

  defines function

    describeNotification()
      -> n as Notification
      <- rtn as String: `notice: ${n.summary()}`

//EOF

delivery.ek9

#!ek9
<!-
  The second file of the shared 'relationships' project: functions that call across both files -
  buildNotifications constructs the three subtypes, deliverAll calls describeNotification
  (notifications.ek9) and NotificationRouter's dispatcher for each one.
-!>
defines module lesson.relationships

  defines function

    buildNotifications()
      <- rtn as List of Notification: List() of Notification
      rtn += EmailNotification()
      rtn += SmsNotification()
      rtn += PushNotification()

    deliverAll()
      -> notifications as List of Notification
      stdout <- Stdout()
      router <- NotificationRouter()
      for n in notifications
        stdout.println(describeNotification(n))
        stdout.println(router.route(n))

  defines program

    Deliver()
      deliverAll(buildNotifications())

//EOF

Step 1: Who deliverAll() calls, and who calls it

Open delivery.ek9, put the caret on deliverAll(), then choose Analyse > Call Graph.

The EK9 IDE once this is done: Open delivery.ek9, put the caret on deliverAll(), then choose Analyse > Call Graph

Step 2: Reading the diagram

deliverAll sits in a rose "you are here" box. Above it, Callers (1): Deliver, the program that calls it. Below it, Calls (5), each bordered in its kind's colour from the key at the top: Stdout() and NotificationRouter() teal - constructors; println mauve - a method; describeNotification green - a function; and below the pane's edge - scroll down to it - NotificationRouter.route in yellow, the dispatcher, with the handler each type reaches beneath it. A dashed border would mark a polymorphic call - one that resolves to more than one target.

The EK9 IDE once this is done: deliverAll sits in a rose "you are here" box

Step 3: A construct's own neighbourhood

Open notifications.ek9, put the caret on EmailNotification, then choose Analyse > Neighbourhood.

The EK9 IDE once this is done: Open notifications.ek9, put the caret on EmailNotification, then choose Analyse > Neighbourhood

Step 4: Reading the edges

Neighbourhood of EmailNotification: 9 constructs and 10 edges, two hops out (Hops 2, Dir both), with EmailNotification in the bold box at the centre. Beside it Loggable, the trait it implements, and around it the functions and the router that use it - describeNotification, deliverAll and NotificationRouter; Notification, the class it extends, and buildNotifications sit further out - scroll down to them. The Kinds row picks which links count: the structural four (extends, implements, composes, references) are ticked; calls, injects and dispatches are not - tick one, or change Hops, and the picture redraws.

The EK9 IDE once this is done: Neighbourhood of EmailNotification

The result

That is Call Graph and Neighbourhood: deliverAll() (delivery.ek9) calls describeNotification() and route() - both declared in notifications.ek9, so the graph crosses the project's two files without being told to - and EmailNotification's neighbourhood showed what it extends and implements and what uses it, two hops out by the structural kinds. Analyse > Call Graph (⇧⌘C) and Analyse > Neighbourhood (⇧⌘B) both read the caret's own construct; AI assistants get the same data from ek9_query_call_graph and ek9_query_construct_neighbourhood.

Where this lesson comes from