Kotlin / KEEP

Kotlin Evolution and Enhancement Process
Apache License 2.0
3.3k stars 356 forks source link

Java annotations for Kotlin sugar #110

Closed JakeWharton closed 3 years ago

JakeWharton commented 6 years ago

Discussion of the proposal in #111

Summary

This proposal adds annotations which enable the use of more Kotlin language features like extension functions, extension properties, default values, and property names for Java code.

  • @ExtensionFunction / @ExtensionProperty - Turn a static method with at least one argument into an extension function or an extension property.
  • @ParameterName - An explicit name for parameters.
  • @DefaultValue - Default parameter values.
fvasco commented 6 years ago

Should we consider @Suspend for coroutine support or @Internal for internal modifier?

ice1000 commented 6 years ago

@fvasco @Suspend is painful, since it requires the compiler to do CPS conversions (which is not supported by javac), and you'll need an annotation processor (and of course, an extremely complicated one) for it.

And BTW @Internal is a great idea.

fvasco commented 6 years ago

@ice1000 suspend is a regular Continuation parameter, it is not really different then extension's one.

I consider KtName more "painful", in the example we have defined a better name for copyOf, but toImmutableSet method is undocumented in JavaDoc. Looking for rootCause how should I can found getRootCause in Throwables class? JavaDoc is not organized for Kotlin syntax.

My only consideration against suspend annotation is a really specific Kotlin type of Continuation. Async libraries use often a different type for CPS argument, AsyncHandler for AWS API, Handler for Vert.x, OperationCompletionListener for Memcached client, Promise in JS, and si on.

Therefore a really common code pattern is:

suspend fun <T> myAwait(block: (MyHandler<T>) -> Unit): T =
        suspendCoroutine { cont ->
            val handler = ContinuationMyHandler<T>(cont)
            block(handler)
        }

followed by the code

val result = myAwait{ handler -> myClass.myMethod(p1, p2, p3, handler) }

Permitting the suspend annotation allows many sympathetic libraries to define

@KtSuspend("mypackage.ContinuationMyHandler")
public void myMethod(String p1, int p2, int p3, MyHandler callback)

so the kotlin code becomes

val result = myClass.myMethod(p1, p2, p3)
fvasco commented 6 years ago

Another tiny consideration: using @KtSuspend("mypackage.MyHandlerContinuation") allows every library to ship code with Koltin support but without any dependencies to Kotlin library (*) (the handler is referenced as a constant string).

So every Java developer has no issue. Every Kotlin developer must include a specific library containing mypackage.MyHandlerContinuation class.

ice1000 commented 6 years ago

So you mean @KtSuspend requires the user to manually pass the Continuation instance? That sounds much more reasonable.

fvasco commented 6 years ago

Maybe is better provide with a @KtSuspend annotation an another one, @KtContinuationAdapter to specify an eventually wrapper (ie mypackage.ContinuationMyHandler), this annotation can be applicable to parameters, methods, types or packages.

@KtSuspend is applicable to all function returning void with at least one parameter, the last one must have the type Continuation. The class referenced by the @KtContinuationAdapter annotation parameter must have a constractor with one argument of type Continuation and that class must implement the method continuation interface, ie:


@KtContinuationAdapter("ContinuationMyHandler")
class AsyncByteBuffer {

  @KtSuspend
  void load(int bytes, MyHandler myHandler) { ... }

}

class ContinuationMyHandler implements MyHandler {

  ContinuationMyHandler(Continuation continuation) { ... }

  ...

}
JakeWharton commented 6 years ago

We are not going to pursue either of those suggestions as part of this proposal. Coroutines aren't stable and the annotations in this proposal are targeted at library APIs where internal isn't useful.

fvasco commented 6 years ago

Hi @JakeWharton I don't agree with your considerations.

If this KEEP becomes stable before coroutine then it may contain experimental annotation, else the coroutine will be defined and the discussed part might be valid (the above annotation requires only a suspend keyword and a Continuation interface).

Finally the @KtInternal annotation shares the same motivation of internal visibility modifier, regardeless the target libraries.

This KEEP is interesting not only for well-know public libraries, but it is applicable to every project with mixed Java-Kotlin code.

JakeWharton commented 6 years ago

The proposal is intentionally limited in scope to increase its chance of success, as evidenced by the last section where we enumerate a few other language features that could easily be included. Attempting to map every Kotlin language feature back into some shape of Java source is an easy and fast way for this proposal to never get implemented.

It is a non-goal to solve Java-Kotlin interop for every project as you state.

fvasco commented 6 years ago

@JakeWharton thank you for quick response, your point of view is reasonable.

The idea behind this KEEP is really interesting, but the most articulate discussed feature, the @KtSuspend annotation, is a great boost for long standing opened issue like this https://github.com/vert-x3/vertx-lang-kotlin/issues/41 Already mentioned libraries and more share the same problem.

I not am an Android developer, I am developing a fully asynchronous server application. I consider asynchronous libraries an emerging trend for server-side develop (see Java NIO, Netty, Spring, AWS Java client, Vert.x, ...), so simplify further the Kotlin interoperation with existing Java libraries is a great benefit.

Can you consider to introduce an optional part for this KEEP or do you consider a better option extending using a new one?

gildor commented 6 years ago

@fvasco @KtSuspend looks like much more advanced feature with a lot of non clear parts such as adpaters. Also coroutines are experimental, so it's one more not clear case (continuation interface will be changed, maybe make sense to wait for this)

If you see good use cases mabe make sense to create a new KEEP

gildor commented 6 years ago

I don't think that internal for Java code is good idea. If some class/method available from Java for some reason no need to overcomplicate it and provide additional visibility rules just for Kotlin. And probably it shouldn't be a part of this proposal.

fvasco commented 6 years ago

Hi @gildor, Coroutines are experimental, it is true, but some libraries are already propose an experimental support for it, so personally I do not consider a real argument against this.

I exposed all reasonable motivation for @KtSuspend annotation, maybe @vietj can expose better consideration about this.

Await coroutine stabilization is a JetBrain choice, so I should consider that this KEEP should be included in Kotlin 1.2 (right?). My idea is to provide a @KtSuspend in Kotlin 1.3, so when the coroutine will be stable.

However if your consider interesting this idea and you would examine in deep the not clear parts then I will open a dedicated KEEP.

opatry commented 6 years ago

Please, 🙏 do not put Kotlin related names in annotations that might be used in standard Java libraries. What I want to create my own language and provide such custom and "colored" annotations? Should every Java libraries authors pollute their own code with @KtName, @FooName, @MyName etc.? Same is for @DefaultValue("myNamingConvention"), what if I do not choose such naming convention in my own language?

This should be defined through a JSR with generic semantics that Kotlin might choose to handle afterwards.

For instance, @Inject (javax.inject.Inject) reflects this, @NonNull from Android don't which is unfortunate, despite the availability of @NotNull (javax.validation.constraints.NotNull).

Correct me if I'm wrong.

Still, I think this initiative is great and trying to improve Java/Kotlin interoperability goes in the right direction, I'm just worried about the implementation and chosen names.

JakeWharton commented 6 years ago

The names have already changed, which I'll update soon, but being Kotlin-specific is not changing.

Otherwise, your own argument works against you:

what if I do not choose such naming convention in my own language?

Any semantics you try to define in some annotations which are common to every JVM language (an impossible task in and of itself, surely) can be trivially undermined by inventing your own language whose semantics are different.

opatry commented 6 years ago

@Name with javax.* package seems reasonable and generic enough to be compliant with other JVM languages, @DefaultValue seems harder and might be avoided IMO.

JakeWharton commented 6 years ago

Just updated the proposal based on 2018-05-10 JetBrains and Google/Android meeting.

Change summary:

  1. Rename @KtName to @ParameterName, scope only to parameters
  2. Add name property to @ExtensionFunction and @ExtensionProperty

See the commit message for some raw notes.

fvasco commented 6 years ago

How import Java extensions?

Ie: for the Java class

class Util {
   @ExtensionProperty String ca = "ca"
   @ExtensionFunction void cm(String p) { }

   @ExtensionProperty static String sa = "sa"
   @ExtensionFunction static void sm(String p) {}
}

should the follow code be valid?

import Util

with(Util()) {
   ca.cm()
}
import Util.sa
import Util.sm
// or import Util.*

sa.sm()

Explicit import made easy understand the related Java classes.

JakeWharton commented 6 years ago

I think this is ultimately for the compiler team to decide, but we talked about requiring an import because otherwise they have to parse a lot of Java to find the extension.

(Also worth noting that @ExtensionProperty only works on methods where the first parameter is the receiver type.)

SUPERCILEX commented 5 years ago

Any status updates from the JetBrains team? This would be extremely useful...

guilhermesgb commented 4 years ago

Any updates?

mochadwi commented 4 years ago

any updates?

elizarov commented 3 years ago

We've discussed this proposal with the team and have decided that it does not worth the effort to implement and maintain. The main users of this KEEP were supposed to be existing, mature JVM libraries and to make it really work for them would have required a considerably large design and implementation effort of providing the appropriate level of backwards compatibility, too.

More than two years of experience since the creation of this KEEP have shown that there are two approaches to adapting libraries to Kotlin that work well for the community:

Approach 1: Rewrite the library to Kotlin, while maintaining binary compatibility for Java clients. For example, this approach was chosen by Square open source libraries (OkHttp, etc) and their experience was mostly positive. Some of Google's Android libraries are also taking this road. Moreover, we plan to continue investing in features that help Kotlin libraries to maintain the desired shape of JVM API surface to make this kind of transition from Java to Kotlin easier. The existing J2K (Java-to-Kotlin converter) tooling helps with this approach a lot and we plan to continue to maintain and improve it.

Approach 2: Provide Kotlin support libraries with adapters that add idiomatic Kotlin APIs to existing Java libraries. For example, this approach was taken by the Spring framework team (see the tutorial). Most of the Android core APIs use this approach via Android KTX, too.

It is worth noting, that the latter approach is easy to automate if needed. For example, instead of bolting on @ExtensionFunction annotation support to Kotlin compiler, one can define their own annotation and write a simple Java Annotation Processor that generates the corresponding Kotlin extension functions in Kotlin automatically. Kotiln inline functions make the corresponding adapter functions essentially zero-overhead, giving way more flexibility than any kind of compiler-provided annotation-driven feature could ever possibly have.