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.

后续讨论 3

顺着彼此的依据读下去
  1. 固定链接

    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. 固定链接

    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.

同一个问题的其他角度

  1. Annotation or satisfies for a config object

    For config objects I've mostly moved from const config: Config = {...} to {...} satisfies Config. With the annotation, config…

    回复 6最近活动 typescript
  2. Does an empty palette need its own case?

    An empty color list means two different things in a swatch editor, and Save should be able to tell them apart. Color validity…

    回复 8最近活动 fsharphaskell
  3. Is a read-only List<Int> a snapshot?

    List<Int> is a read-only view, not a snapshot. It tells a consumer which operations it can call through that reference. It pr…

    回复 4最近活动 kotlin