Internal function parameter name

I’ve been getting into Swift recently and noticed that it is incredibly similar to Kotlin. Something that Swift has and I think would be of great help for the Kotlin community to improve code legibility even more is the addition of internal function parameter names (referred as ‘argument labels’ in Swift’s documentation) to Kotlin.

So, for example, if we have a function to greet someone, we’d do something like this in Kotlin:

fun main() {
    greeting(name = "Alex")
}

fun greeting(name: String) {
    println("Hello, $name!")
}

But, if we have argument labels, it can be converted to something more legible:

fun main() {
    greeting(for = "Alex")
}

fun greeting(for name: String) {
    println("Hello, $name!")
}

The argument label (represented as for) is used when the function is called, and the parameter name (represented as name) is used only inside the function itself. I think this tiny change can lead to a great impact on code legibility and would be a great feature to have in a future release of Kotlin!

4 Likes

Doesn’t it get confusing having two names for a parameter, one used by the caller and one inside the function?

And which name would the function’s doc comment use? The internal one would make it confusing for callers to read, while the external one would be different from current practice and consuing when reading the implementation.

In my experience with Kotlin so far, using named parameters isn’t very common. It’s handy for when there are multiple optional parameters, or when the function isn’t clear from the context. (String.equals(... ignoreCase = true) illustrating both!) Also when a function takes too many parameters to keep them clear, though that’s a code smell and should probably be rewritten.

And Kotlin has a fair number methods with names that have been chosen to read well without a parameter name (e.g. takeIf(), removeAt()).

So my gut feeling is that the benefit to Kotlin would be too small to outweight the potential confusion. Does your experience indicate otherwise?

2 Likes

There has been a discussion about this before with some example use cases:

3 Likes

As that would be a significant change on the way we write functions, it would require a readjustment regarding the function’s documentation. I think the most reasonable way would be to refer to the parameter using its internal name, as it is, obviously, an internal name and the documentation is something considerably internal as well.

As stated in @Wasabi375’s linked example, by @Weikardzaena, there are cases where naming these parameters gets extremely confusing, because you have to choose between either making it legible externally (when calling the function) or internally, reducing the readability externally. Labeled parameters would be the perfect solution!

In my experience, even in the simplest cases, it gets hard to decide between one approach or another. As an Android developer, for example, I tend to use:

FragmentContainerView.replaceBy(fragment: Fragment, manager: FragmentManager) = manager.beginTransaction().replace(this.id, fragment).commit().

It would be nice, though, to be able to simplify its declaration using:

FragmentContainerView.replace(by fragment: Fragment, using manager: FragmentManager) = manager.beginTransaction().replace(this.id, fragment).commit()

…and to call it as:

containerView.replace(by = someFragment, using = fragmentManager)

At the end, the declaration got even more legible than it already was before.

At first glance a call like .replace(by = someFragment, using = fragmentManager) does read very well — but as soon as you look more closely, it gets confusing…

In that example, instead of parameters being named as nouns (or noun phrases), which is traditional and makes perfect sense for objects and other values, they’re named as prepositions, adverbs, or similar. Which means you lose the direct correspondence between the value and its name.

What is a ‘by’? What does it mean to set a ‘by’ to something, or for something to act as a ‘by’? It’s meaningless. Similarly, if you’re writing a function call and get to the point where you have to pass a ‘using’, what does that tell you? Very little. You’d have to look up the type to make any sense of it — which you generally don’t have to for noun parameters.

I appreciate that in some cases it can be useful to add some sort of explanatory text to parameters. (That’s pretty rare in my experience; I guess yours must differ markedly.) But a parameter name doesn’t look like the right solution.

I can’t speak knowledgeably about your example, as I don’t do Android (or whatever that is), but I wonder whether that sort of situation is what ‘fluent’ interfaces were intended for. How about something more like .replace(someFragment).using(fragmentManager)?

Or could you imagine some other new language feature which made logical sense in addition to reading well?

4 Likes

Personally I don’t like the thought using different names internally and externally. As @giggs hinted, the parameter by would tell nothing about the real argument. One has to know the function to know what the arguments stands for.

It was also suggested that with fluent API very similar solution could be achived. (However, a few week ago, in a post I was convinced that fluent API is rarely needed in Kotlin and one should use apply/also instead.)

I know it is far fetched, but in some cases, to make it more readable outside, you could give up some of the readability of the internal implementation using micro-dsl. (It is more like a thought exercise than practical solution.)

class A {
    override fun toString() = "A"
}

infix fun A.replace(v : String) = Pair(this, v)
infix fun Pair<A, String>.using (i : Int) {
    println("${this.first} replaced by ${this.second} using $i")
}

(I used Pair only for quick prototyping.)

Now one could write:

    val a = A()
    a replace "x" using 5

But, does it really give enough to the cause justifying implementation complexity?

I wonder then if a mini DSL would be a good alternative. You could setup a DSL where by you would call it like:

containerView.replace { by = someFragment, using = fragmentManager }

Though this is more work on the internal side

1 Like

Instead of such a tear-up to the function declaration syntax would probably be better to do as an annotation on a parameter:

FragmentContainerView.replace(
    @externalName("by") fragment: Fragment,
    @externalName("using") manager: FragmentManager) =
1 Like

You could even have the “legible” names on the outside then inside the functions just reassign them like this:

FragmentContainerView.replace(by: Fragment, using: FragmentManager) {
    val fragment = by
    val manager = using
    manager.beginTransaction().replace(this.id, fragment).commit()
}

(This can obviously be implemented as a compiler plugin)

I think this proposal makes perfect sense. IMHO it is not confusing at all, especially because the mapping between the (readable) external label and the internal label is part of the function signature. I use this in Swift all the time.