Kotlin internal and external parameter name propose

I like this in Swift, is very verbouse, please add in Kotlin to.

class User() {
    
    fun send(notification: Notification, using token: String) {
       // 1. Name of function is short and clear
       // 2. Inside of function body you use token, not a long parameter for exemple: userAccountDto.
    }
}

// When you call this function is self explanatory.
val notification = Notfication()
val token = "your_token"
User().send(notification, using: token)
5 Likes

Do you also want to explain what this is? I guess most people have not used Switft before and have no idea what you are talking about

3 Likes

Looks like just named parameters, but when you have one name for parameter inside function and another from outside. Not sure what this is good idea.

2 Likes

This function is an exemple of how to use. By having this feature functions can become lets say more verbouse. What I miss from Swift in Kotlin (both languages are very similar) is that you can explain more what the function is doing and with what king of input. Lets say you have:

class User() {
    
    fun send(notification: Notification, using token: String) {
       // 1. Name of function is short and clear
       // 2. Inside of function body you use token, not a long parameter for exemple: userAccountDto.
    }
}

// When you call this function is self explanatory.
val notification = Notfication()
val token = "your_token"
User().send(notification, using: token)

Also give opportunity to chose if we want to show or hide parameters and Not specify enum classname. Exemple :

enum class UserNameType {
    nickname, emoji
}

class User() {
    
    fun convert(_ name: String, into type: UserNameType) {
       // 1. _ notification = optional parameter name.
    }
}

User().convert("Name_string" into: .nickname)

I`m not shure that give the right example, but its like programing language became more natural and uses less code to write.
For more details please check: swift functions

2 Likes

Interesting. So if I understand this correctly in swift each argument has both a name and a label. The name is what you use to refer to the argument from within the function and the label is what is getting used when you refer to the argument when calling the code.

func someFunction(label name: Int)  -> Int{
    return name    // here I have to use the name
}
someFunction(label: 5)  // when using named parameters I have to use the label
someFunction(5)           // I think I can just call this the "normal" way as well

I hope I did not misrepresent the feature.


I can see where this could come in handy, especially when designing DSLs. The problem I see however is how this is supposed to work with reflection as each parameter now has 2 names. I guess you would return the label and not the name.
Also I dislike the idea that 2 parameters can have the same label. According to the doc in swift I can have 2 parameters with different names but the same label. IMO this should be changed to both labels and names should be unique per function. That way there is no problem with the java interop.

No, parameter name is only one, label is separate. Can be realized like optional.

// Label by default will be hiden.
func someFunction(_ name: Int)  -> Int { return name }

// So in call you will have
func someFunction(123)
// In this case reflection is only made by parameter name 

// Also you can specify the label
func someFunction(label name: Int)  -> Int { return name }

func someFunction(label:123)

What about reflection, you only make it using parameter name. I think in DSLs you can specify that the label have the same value as parameter, so reflection can work in this case.

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 :rocket: I recognize that language design is very complex, so please give feedback as to whether this idea is solid :slightly_smiling_face:

7 Likes

This feature has been wished for quite often in this forum by developers coming from Swift. But this is the first time it comes with such in-depth explanation. Thanks @Weikardzaena. I clearly can see how Kotlin could benefit from this. Introducing it seems to be easy if we consider the existing parameter names to be the external labels, then we would just need a way to optionally define internal private parameter names.

2 Likes

I’m glad this is getting more attention. There are a lot of things I see across the industry, like taking a specialized object instead of a few parameters (or constructors with no arguments which expect you to set fields immediately after), which are there solely to make it clearer what’s being passed in.

send(message, 80, true, 0.5)

This is not at all descriptive of what’s actually happening. Some languages will have you do this:

var params = SendParameters()
params.message = message
params.port = 80
params.isSecure = true
params.delayInSeconds = 0.5
send(params)

Others might have you do this:

send({
    message: message,
    port: 80,
    isSecure: true,
    delayInSeconds: 0.5
})

Swift and Objective-C solved this problem by letting API designers enforce parameter labels:

send(message: message,
     toPort: 80,
     securely: true,
     delayInSeconds: 0.5
)

Kotlin allows you to put the parameter’s name in the call, which does improve the clarity bit for the API user, but it doesn’t allow you to have a separate external label, nor to force the caller to use a label. Because Swift had this from day 1, it can differentiate functions based on their labels:

// This function might print the given string to the console.
//
// Do note that the name of the parameter and the external label
// are the same here, since no label is explicitly given, nor is
// one omitted
func print(message: String) -> Void { ... }

// This function might send the document at the given path to
// an inkjet printer to be printed.
//
// Do note that if the parameter name was used instead of the
// special external label, it might seem like this function just
// prints the path, instead of the whole document.
func print(documentAt documentPath: String) -> Void { ... }

These are both print functions which take one string and return nothing. Despite that, Swift has no problem distinguishing them at the use-site, since their mandatory parameter labels are different.

I find it odd that lots of languages have these weird de-facto workarounds to make parameters more clear, but then they also refuse to implement parameter labels. Kotlin would be a much better language for API design if API writers could make their functions more clear and expressive by using these labels.

This also takes a lot of text out of the name of the function before the parenthesis. This both makes the use site more terse, and also more clear. Compare these:

func open(file: File, in application: Application) -> Void { ... }

open(file: myFile, in: microsoftWord)
fun openFileInApplication(file: File, application: Application): Unit { ... }

openFileInApplication(myFile, microsoftWord)

I have an example similar to @Weikardzaena’s example.

In Android, I keep all my intent-starting code inside a class named ActivityRouter:

//the splash screen
class SplashScreen : AppCompatActivity()
{
     fun onSplashDelayFinished()
     {
          ActivityRouter.startLoginActivity(from = this)
     }
}
//the login screen
class LoginActivity : AppCompatActivity()
object ActivityRouter
{
     fun startLoginActivity(from : Context)
     {
           val i = Intent(from, LoginActivity::class.java)
           from.startActivity(i) //awkward
     }

     //versus....
     fun startLoginActivity(from context : Context)
     {
           val i = Intent(context, LoginActivity::class.java)
           context.startActivity(i) //great
     }
}
1 Like

One thing Kotlin does give you is the type system. You could define your function like this:

inline class Message(val text: String)
inline class Port(val port: Int)
enum SecurityType { Secure, Insecure }
class Delay { ... }
fun send(message: Message, port: Port, securityType: SecurityType, delay: Delay)

You know just from the type exactly what each parameter needs to be. Also has the advantage of leaning on the compiler to make sure you’re passing in good values.

1 Like

Creating a thin wrapper around a primitive just for naming sake does not seem like the correct solution to solve the issue of clarity and parameter data integrity.

fun send(message: String)

Is completely identical to

inline class Message(val text: String)
fun send(message: Message)

Except that now you have two lines instead of one, and a rigid structure depending on a strongly-typed wrapper Message that doesn’t add anything to the implementation. Every caller of the function send() would need to depend on this wrapper just for the sake of naming, which doesn’t seem like a good idea.

Of course, there are reason to wrap things like Port in your example for data integrity:

class ValidPort(port: Int) {
    val port: Int =
        if (port < 0 || port > 65535)
            /* Handle invalid port */
        else
            port
}

But that’s beside the point.


This solution also doesn’t actually address the original issue, which is to use parameter names (or labels as this thread proposes) to clearly define what the caller is expecting that parameter to do.

For example, naming a parameter delay doesn’t really tell the caller what that delay is for. Is it delaying before sending? Is it threshold for how long to delay if there are other requests active? You could name it something like, withDelayBeforeSending: Delay, but then the implementation of this method would be forced to use that long name internally. I could imagine something like this:

class NetworkImpl {
    fun send(message: String,
             port: ValidPort,
             securityType: SecurityType,
             withDelayBeforeSending: Delay) {
        
        val msDelay = withDelayBeforeSending.toMillis() // awkward
        // ...
    }
}

I personally think this would be cleaner:

class NetworkImpl {
    fun send(message: String,
             port: ValidPort,
             securityType: SecurityType,
             withDelayBeforeSending delay: Delay) {
        
        val msDelay = delay.toMillis() // much easier to read
        // ...
    }
}
2 Likes

Disclaimer: paranoid object oriented programmer opinion here.

Personally, this feature looks confusing. It makes function reading more complex by having to distinguish/check label and name both from user side (Ok, where is the label and where is the name already ? Which one can I use ?), and from maintainer side (name shallowing, not uniform naming between user call and parameter usage).

I prefer type wrapping, as explained in the KotlinConf topic Power of types. For me, it provides better evolutivity (adding validation properties or extending behavior becomes simpler).

In the provided examples, I agree that at first look it is appealing. However, I’m afraid that such syntactic sugar would allow shortcuts that are neither good for code maintainability nor resilience.

The perfect example is the print function by @BenLeggiero which clearly prevents compiler to make safe assumptions about user choice (if I made an error by choosing the print with a label, only runtime error will reveal my mistake). Also, I’m a little afraid of IDE auto-completion, which will have to display clearly the label to avoid any confusion.

Moreover, putting multiple parameters in an object is not useless wrapping, as it allows context grouping, to propagate more easily function execution context (for logging or persistence purpose).

In most of the cases above, I would consider tools like Context programming or type-safe builder instead. They really promote separation of concern and type safety, but also leave a lot of room for expressiveness. I’ll try to refactor the notification example to illustrate:

User notification

// Isolate token so it's not dangling around
class SecurityService {
  fun authenticate(token: Token) = SecurityArea(token)
}

class SecurityArea {
  infix fun User.send(notification : Message) {...}
}

val authDone = security.authenticate(myToken)
...
val arthur = User()
// More verbose, but notification is only allowed from safe place, and infix keeps it pretty "natural"
with(authDone) {
    arthur send Message("Hello !")
}
1 Like

Of course! Kotlin’s fairly-strong type system does help a lot.

But imagine if I also had this:

fun send(backupMessage: Message, message: Message, port: Port, securityType: SecurityType, delay: Delay) { ... }

Where backupMessage might be one that’s sent if the first one fails or something like that. This order might be reasonable when this API is placed next to the one in your example, since leaves all other parameters in the same order, and also places the failure state (which might be a constant) up front. However, one might misunderstand the order of the parameters:

// Maybe somewhere in a broader scope:
val backupMessage = Message("That didn't go well")

// Then:
send(Message("Hello, Riley!"), backupMessage, Port(80), Secure, Delay.brief)

If the parameter labels for these first two were required, then this usage would be unambiguously wrong:

send(backupMessage: Message("Hello, Riley!"), message: backupMessage, Port(80), Secure, Delay.brief)

I would really love this syntax. Unfortunately, someone at JetBrains thought it would be a great idea to reserve this for parameter modifiers:

send(vararg message: String)
send(noinline message: () -> String)
send(crossinline message: () -> String)

so we can’t use that as the label syntax :tired_face:

This idea doesn’t preclude using types for better compile-time assurance of behavior. In fact it works well with that, in tandem making it nigh-impossible to accidentally pass wrong values to any API.

I’m not sure why you think this makes the compiler incapable of choosing a function. The approach I imagine would be perfectly distinguishable at compile-time. The JVM name of these functions might include the parameter names, for instance. I’m not a JVM engineer but I also imagine that functions can have metadata about this too, which would allow them to be selected unambiguously.

IDEA already displays the labels for parameters :stuck_out_tongue:

Single-use types have many advantages, yes. I do advocate for not being afraid of them, and to use them wherever appropriate. However, I think that solely using them so that it’s unambiguous which parameters you’re passing to some API is not an appropriate reason to use them.

Also, I think your example is cute and demonstrates some of the syntactic sugar that can be achieved with Kotlin internal inline extension functions, but I can’t tell if your code tells arthur to send a message, or if it’s sending a message to arthur, so maybe it needs some workshopping before it’s an example of expressive code, since I can’t tell what you’re expressing with it. It’s also neither an example nor a counter-example for why Kotlin should have parameter labels.

I actually like the idea. It should even be somewhat easy to add it to the language if we use the as keyword:

fun myFun(externalName: String="default" as internalName)

I must say that I have had cases where I have hit this naming issue.

5 Likes

Are there any news on this? This would really allow for way more readable code.

Sadly Android Studio would still remove the labels and show send(message, 80, true, 0.5), but that’s to discuss on another platform…

1 Like

Just to throw in another example. I’ve added the external name as if this were Swift.

Here I want the external parameter account to match the name of the instance variable, just for simplicity and clarity. Within the function, however, I need to give it a more specific name to distinguish it from an account specified by the environment. Showing the parameter name injectedAccount outside this function just clutters up the call site with redundant information.

fun init(context: Context, **account** injectedAccount: Account? = null) {

    val manifestAccount = loadAccountFromManifest(context)

    if (injectedAccount != null) {
        this.account = injectedAccount

    } else if (manifestAccount != null) {
        this.account = manifestAccount

    } else {
        logger.error("Corrupt or missing license details")
    }
}

I’m fairly new to Kotlin so it is entirely possible I’m not doing this in a idiomatic way, please be kind :wink:

In Kotlin you could write this.account=account. Isn’t that possible in Swift? Do you find this less optimal than using a labeled argument?

1 Like