openlang

A helper ported from Java picks a different overload in Groovy

Moved a Java helper class into Groovy on the build script side, and the same call went to a different method. Laid out in order.

class Printer {
    void show(Object o) { println 'object' }
    void show(String s) { println 'string' }

    void run(Object x) { show(x) }
}

new Printer().run('hi')

In Java the declared type of x is Object, so show(Object) is chosen at compile time and it prints object. Groovy picks the method at run time from the actual argument type, so the same code prints string. The docs describe it under the name multi-methods, so it isn't a bug.

The confusing one is the third case. Put @CompileStatic on the class and it prints object again. So the same source calls a different method depending on one annotation. I have a few more helpers with overloads split by type, and I'd like to know whether people put @CompileStatic per file or per class, or whether the right move is to get rid of the overloads altogether.

The conversation continues 3

Follow the reasoning
  1. Permalink

    What decides it is the compilation mode of the call, not of the class that declares show. In your example they're the same class, so it doesn't show. Move the two overloads into a @CompileStatic utility class and call them from a dynamic script, and the script's call is still dispatched at run time, so it prints string. The annotation has to cover the code that makes the call.

    So per class only helps when the callers live in that class. For helpers called from build scripts I'd rather remove the ambiguity than annotate around it. Two overloads that differ only by a subtype are where the static and dynamic choices diverge. Two names, or one method that branches on the type itself, give the same answer in both modes, and the cost is a slightly longer name at the call site.

  2. Permalink

    I'm on the renaming side too. There's one more option, though. Groovy's switch has always accepted a class as a case, so you can branch on the run-time type without leaning on dispatch at all.

    void show(Object o) {
        switch (o) {
            case String:
                println 'string'
                break
            default:
                println 'object'
        }
    }

    With a single method there's no overload to choose, so it prints string with or without @CompileStatic. If it ever moves back to Java it turns into an instanceof pattern almost line for line. Going from two methods to one is a plus for me on its own.

Other sides of the same question

  1. Units of measure as cheap ids

    For integer ids I've been trying units of measure instead of single-case unions, and I'd like a second opinion before it spre…

    Replies 1fsharp
  2. An empty cell equals zero and the empty string

    Inherited a macro that flags order rows with a zero quantity, and it flagged every blank row too. The check was If ws.Cells(r…

    Replies 7vba
  3. Validating JSON before the discriminated union

    A type assertion only tells the compiler what to believe. An external response starts as bytes and says nothing about its var…