"Shadow" classes - typealias+extensions with teeth?


#1

I have no idea what to call these or if this concept already has a name but “shadow class” sounded cool at the time. :slight_smile:

I think a way to make a shadow API “instance” around a type without creating a real instance could be really useful. Two main situations come to mind:

  1. Kotlin-specific extensions that would like to more naturally constrain certain parts of the underlying and/or API or reuse names
  2. Multiplatform APIs, where you may have a native construct with different APIs across different platforms, but you would still like to write efficient, bidirectionally interoperable code around those objects. For example, the Protobuf API will differ quite a bit between JVM/JavaScript/iOS, but Kotlin xplat business logic would benefit greatly from being able to use a single type and API for those messages.

Here’s what it might look like:

pre-existing java:
class Foo extends GeneratedMessageLite<Foo> {
  Bar getBar()
  boolean hasBar()
 
  Baz getBaz()
  boolean hasBaz()
 
  int getSize()
 
  void doThing()
}
 
kt:
 
// Members of Foo are hidden 
shadow class FooKt(delegate: Foo) {
  val size = delegate.size 
 
  val bar: Bar?
    get() = if delegate.hasBar() delegate.bar else null
  val baz: Baz
    get() = delegate.baz
 
  fun doThing = delegate::doThing
  fun doOtherThing() { ... }
}

To the developer, after constructing one with FooKt(myFoo), a FooKt object would look, act, and smell like its own object. To the compiler, it would be similar to a Foo typealias with its own API. it would be similar to statically resolved extensions, except syntactically they would be treated as instance methods and properties on this new virtual type.

This could of course be used with expect/actual for multiplatform implementations, allowing you quite a bit more interop with assymetric APIs without wrapping objects at the boundaries. The expect part would not need to know what the underlying object type is, as long as there is a single implementation provided. That does mean while using shadow objects in pure Kotlin you could create and convert between them and their shadowed type, but when used as an expect/actual pair the conversion would only happen within the ‘actual’ impl code.

This idea somewhat would somewhat obviate the need for my previous post. Although it would require a layer of codegen to have the same effect for something like protobufs, it would be more explicit in design.


#2

This looks like the inline class idea (see What are inline classes)