elasticsearch
Loading

Understanding method dispatching in Painless

Serverless Stack

Painless uses a function dispatch mechanism based on the receiver, method name, and arity (number of parameters). This approach differs from Java, which dispatches based on compiled-time types, and Groovy, which uses runtime types. Understanding this mechanism is fundamental when migrating scripts or interacting with Java standard library APIs from Painless, as it helps you avoid common errors and write more efficient, secure scripts.

Before diving into the dispatch process, here a brief definition of the main concepts:

  • Receiver: The object or class on which the method is called. For example, in s.foo(a, b) the variable s is the receiver
  • Name: The name of the method being invoked, such as foo in s.foo(a, b)
  • Arity: The number of parameters the method accepts. In s.foo(a, b) the arity is 2
  • Dispatch: The process of determining which method implementation to execute based on the receiver, name, and arity
Flowchart showing five steps: s.foo, receiver, name, arity, and execute method

This fundamental difference affects how you work with Java APIs in your scripts. When translating Java code to Painless, methods you expect from the standard library might have different names or behave differently. Understanding method dispatch helps you avoid common errors and write more efficient scripts, particularly when working with def types that benefit from this optimized resolution mechanism.

The consequence of the different approach used by Painless is that Painless doesn’t support overload methods like Java, leading to some trouble when it allows classes from the Java standard library. For example, in Java and Groovy, Matcher has two methods:

  • group(int)
  • group(string)

Painless can’t allow both of these methods because they have the same name and the same number of parameters. Instead, it has group(int) and namedGroup(String). If you try to call a method that is not exposed in Painless, you will get a compilation error.

This renaming pattern occurs throughout the Painless API when adapting Java standard library classes. Any methods that would conflict due to identical names and parameter counts receive distinct names in Painless to ensure unambiguous method resolution.

We have a few justifications for this different way of dispatching methods:

  1. It makes operating on def types simpler and, presumably, faster. Using receiver, name, and arity means that when Painless sees a call on a def object it can dispatch the appropriate method without having to do expensive comparisons of the types of the parameters. The same is true for invocation with def typed parameters.
  2. It keeps things consistent. It would be genuinely weird for Painless to behave like Groovy if any def typed parameters were involved and Java otherwise. It’d be slow for Painless to behave like Groovy all the time.
  3. It keeps Painless maintainable. Adding the Java or Groovy like method dispatch feels like it’d add a lot of complexity, which would make maintenance and other improvements much more difficult.

For more details, view the Painless language specification and the Painless API examples.