I was a little bit surprised that this code compil...
# getting-started
d
I was a little bit surprised that this code compiles:
Copy code
fun test() = 1

fun test(x: Int = 1) = 1

fun main {
  test() // which function is actually called?
}
Wouldn't it be more sensible (honoring the principle of least surprise) to actually throw an "ambiguous resolution" error?
h
According to the Kotlin Language Spec, it's not ambiguous: https://kotlinlang.org/spec/overload-resolution.html#algorithm-of-msc-selection
Any non-parameterized callable is a more specific candidate than any parameterized callable
So with the same number of parameters and the same number of default values, it would be ambigous:
Copy code
fun test(x: Int = 1, y: String = "foo") = "bar"

fun test(x: Int = 1, y: Int = 2) = 3

fun main() {
    println(test()) // compile error: Overload resolution ambiguity
}
d
Of course, if it compiles it isn't technically ambiguous. My question is whether it shouldn't be because from the user's perspective it isn't immediately clear which version gets called.
v
It is immediately clear. The one with less pre-filled parameters
e
overload resolution always favors more specific overloads
d
The idea is you can call the function with parameter in a some other way (pass argument), but you can't call the first one in other way So reporting ambiguity here will make the first function uncallable by any means
r
It isn't immediately clear to me. After 24 years of software development in Java, Groovy, Scala & Kotlin I don't immediately know; I need to either test it out, or decompile it, or go looking for answers in specs /google. My immediate guess would have been that
fun test(x: Int = 1)
is syntax sugar for
fun test(x: Int = 1) = 1; fun test() = test(1)
, and so I'd have been surprised that you could also define
fun test()
separately.
e
the fact that it only does that with
@JvmOverloads
should indicate that it's not, IMO…
d
Now I realize that this lenient overload feature isn't only surprising to some but also could be quite dangerous. What if I have only
fun test(x: Int = 1) = 1
, so
test()
returns 1. But if I now add
fun test() = 2
suddenly my
test()
code returns 2. Well, you could argue that's the principle of overloading and that's what we are used to in OOP but for top-level functions I find it quite surprising.
e
top-level functions are always subject to overload resolution though? even without defaults, you could write a
Copy code
fun test(x: Any) = 1
and later add a
Copy code
fun test(x: Int) = 2
which changes the resolution of existing code
r
> the fact that it only does that with
@JvmOverloads
should indicate that it's not, IMO… I don't think knowing about the behaviour of platform specific annotations is immediately obvious, either - I do, because I've done a lot of gradual migrations from Java to Kotlin over the last 5 years and still have a load of Groovy tests that interact with Kotlin code, but anyone writing pure Kotlin could go a long time before encountering it. I think it's more obvious that something more complicated than simple overloading is going on when you consider the case of multiple defaulted parameters with the same type - since Kotlin allows positional calling,
fun test(x: Int = 1, y: Int = 2): Int = 1
cannot simply be syntax sugar for this:
Copy code
fun test(x: Int, y: Int): Int = 1
fun test(x: Int): Int = test(x, 2)
fun test(y: Int): Int = test(1, y)
fun test(): Int = test(1, 2)
because the 2nd & 3rd evidently conflict when called with positional parameters.
But it still requires a bit of thought.
v
Honestly, I'd say it is almost the same as
fun test(x: Any)
vs.
fun test(x: Int)
. If you call
test(1)
then it is not immediately obvious which one is used as both match. Documentation, experience, and gut feeling tell me it is the more specific
Int
one. Just the same documentation, experience, and gut feeling tell me the one without parameter is preferred, as it directly matches what the user tried to call and needs less parameters defaulted.
But you can never match every developers expectations. 🤷‍♂️
d
The question probably is whether there are strong enough use cases for these not-so-clear-for-everyone cases that outweigh the potential confusion.
e
I don't think it's confusing. Swift has function overloading and default arguments, with the same non-ambiguous resolution result in this case. the exact rules about what is more specific may change, but specificity wins