I just found this and I want to bump it for another round of consideration. This could be very useful for self-documenting, readable API code. For instance, here’s a (very stripped-out) API I’m working on that could benefit from this feature. It’s a Kotlin module for abstracting network requests which uses Retrofit and RxJava.
The API public definitions:
interface Repo {
fun startRequest(query: String,
successAction: () -> Unit,
failureAction: (Throwable) -> Unit,
observeOn: Scheduler) /* <-- Okay so far... */
}
The client code could look something like this:
val repo = Repo()
repo.startRequest(
query = "Foo",
successAction = { /* ... */ },
failureAction = { throwable -> /* ... */ },
observeOn = Schedulers.computation() /* <-- Very clear what this does */
)
But the Internal implementation of the public API is a little awkward:
internal class DefaultRepo : Repo {
override fun startRequest(query: String,
successAction: () -> Unit,
failureAction: (Throwable) -> Unit,
observeOn: Scheduler) {
val disposable = retrofitApi.fooRequest(query = query)
.observeOn(observeOn) /* <-- Awkward */
.doOnSuccess(successAction)
.doOnError(failureAction)
.subscribe()
}
}
In the above example observeOn = Schedulers.compuation()
is very clear, but observeOn(observeOn)
is awkward. One way to get around this is is to rename the parameter:
Public API:
interface Repo {
fun startRequest(query: String,
successAction: () -> Unit,
failureAction: (Throwable) -> Unit,
scheduler: Scheduler) /* <-- Renamed from 'observeOn' */
}
Now the internal implementation looks like this:
internal class DefaultRepo : Repo {
override fun startRequest(query: String,
successAction: () -> Unit,
failureAction: (Throwable) -> Unit,
scheduler: Scheduler) {
val disposable = retrofitApi.fooRequest(query = query)
.observeOn(scheduler) /* <-- Much cleaner, but... */
.doOnSuccess(successAction)
.doOnError(failureAction)
.subscribe()
}
}
But the client code now looks like this:
val repo = Repo()
repo.startRequest(
query = "Foo",
successAction = { /* ... */ },
failureAction = { throwable -> /* ... */ },
scheduler = Schedulers.computation() /* <-- Not clear what this does */
)
To get the best of both worlds, parameter labels could be used to make everything very clear:
Public API:
interface Repo {
fun startRequest(query: String,
successAction: () -> Unit,
failureAction: (Throwable) -> Unit
observeOn scheduler: Scheduler) /* <label> <parameterName>: <Type> */
}
Internal Implementation:
internal class DefaultRepo : Repo {
override fun startRequest(query: String,
successAction: () -> Unit,
failureAction: (Throwable) -> Unit,
observeOn scheduler: Scheduler) {
val disposable = retrofitApi.fooRequest(query = query)
.observeOn(scheduler) /* Use parameter name internally */
.doOnSuccess(successAction)
.doOnError(failureAction)
.subscribe()
}
}
And finally, client code:
val repo = Repo()
repo.startRequest(
query = "Foo",
successAction = { /* ... */ },
failureAction = { throwable -> /* ... */ },
observeOn = Schedulers.computation() /* <-- Very clear API */
)
This outlines a very simple example of the benefit to adding support for parameter labels like in Swift
I recognize that language design is very complex, so please give feedback as to whether this idea is solid 