Emulating Static Duck Typing with Kotlin

The other day, I saw someone ask how you can accomplish something like static duck typing in Kotlin. Their background was in C++. I suspect they wanted to know how to accomplish something like this:

class Bird {
public:
  auto fly() -> void;
};

class Plane {
  auto fly() -> void;
};

template <typename T>
auto fly(T const& obj) -> void {
  obj.fly()
}

auto main(int argc, char* argv[]) -> int {
  Bird bluejay;
  Plane cessna172;

  fly(bluejay);
  fly(cessna172);

  return 0;
}

The idea here that you have two types, that do NOT share a class hierarchy, but still have funtional similarity. In this example, you have birds that can fly and planes that can fly, but birds and planes don’t have anything common otherwise. In the C++ example, you can use standard templates to treat the objects the same in a generic context. C++ generates the code in place at compile time. Kotlin, however, does not and generic functions work entirely differently.

In Kotlin, the way this is typically handled is by creating an interface and implementing that interface in your unrelated objects. However, I discovered that modifying the base classes was impossible. Given that restriction, I think this is probably the best solution that I could come up with:

class Bird {
  fun fly() {}
}

class Plane {
  fun fly() {}
}

interface Flyable {
  fun fly();
}

fun Bird.toFlyable() = object : Flyable {
  override fun fly() = this@toFlyable.fly()
}

fun Plane.toFlyable() = object : Flyable {
  override fun fly() = this@toFlyable.fly()
}  

fun <T : Flyable> fly(obj : T) {
  obj.fly()
}

fun main(args: Array<String>) {
  val bluejay = Bird()
  val cessna172 = Plane()

  fly(bluejay.toFlyable())
  fly(cessna172.toFlyable())
}

The part that makes this workable is the extension functions that force the objects into implementing the Flyable interface. Since Kotlin generics are just like Java generics, where the generic type is erased at runtime, this is pretty much the only way you can accomplish this task. I’m sure there’s other ways, but I think this is probably the safest way. You still have type safety enforced by the compiler, and you only pay a minimal runtime overhead for the creation of the object that essentially captures a reference to the original type and implements the correct interface.

If you look at the bytecode that is generated, it might help to understand how this works with the generic functions:

public final static INNERCLASS toFlyable$1 null null
public final static INNERCLASS toFlyable$2 null null

public final static toFlyable(LBird;)LFlyable;
  L0
    ALOAD 0
    LDC "<this>"
  L1
    NEW toFlyable$1
    DUP
    ALOAD 0
    INVOKESPECIAL toFlyable$1.<init> (LBird;)V
    CHECKCAST Flyable
  L2
    ARETURN
  L3
    LOCALVARIABLE $this$toFlyable LBird; L0 L3 0
    MAXSTACK = 3
    MAXLOCALS = 1

public final static fly(LFlyable;)V
  L0
    ALOAD 0
    LDC "obj"
  L1
    ALOAD 0
    INVOKEINTERFACE Flyable.fly ()V (itf)
  L2
    RETURN
  L3
    LOCALVARIABLE obj Flyable; L0 L3 0
    MAXSTACK = 2
    MAXLOCALS = 1

public final static main()V
  L0 NEW Bird
    DUP
    INVOKESPECIAL Bird.<init> ()V
    ASTORE 0
  L1
    ALOAD 0
    INVOKESTATIC toFlyable (LBird;)LFlyable;
    INVOKESTATIC fly (LFlyable;)V
  L2
    RETURN
  L3
    LOCALVARIABLE bluejay LBird; L1 L3 0
    MAXSTACK = 2
    MAXLOCALS = 2

I’ve removed a bit of stuff that isn’t entirely relevant, but the functionality is still visible. Inner classes are generated for the objects created by the extension functions, which are then generated by the extension functions and then passed to the type erased generic function.

The byte code for the two extension functions is practically identical, however, I’m not sure there’s really anything that can be done about it, at least on the JVM. Could we do the same thing by statically generating the byte code for the fly function at compile time? Probably, but I don’t think anyone has proposed a KEEP to sort it out, and I think the Kotlin creators aren’t really keen on adding macros or anything like that to the language (even though I’d like to see some sort of option for generics with structural typing and some sort of macro system.) Anyway, if you have this issue, and come across this post, I hope this helps.