Visibility modifiers on default parameters

For example:

const val MaxLength = 9 //Example number
fun Add (x : Int, private pos: Int = MaxLength) {...}

would work exactly like:

const val MaxLength = 9 //Example number
private fun Add (input: Int, pos:Int) {...}
fun Add (x : Int) = Add(input, MaxLength)

I could have written it as: fun Add (input: Int, pos: Int = MaxLength), but then I expose my pos parameter.

Visibility modifiers on default parameters would work similarly to how visibility modifiers work on properties with custom getters/setters: Default parameter visibility must be the same or less permissive than function visibility.

6 Likes

I like this idea and have often ran into this issue using tail recursive functions where you need to pass additional state on each iteration

1 Like

Great idea. Anybody got a counter example where this could be misleading?

Don’t think there are any. The only question is, should this also support protected and internal and if yes how would you be allowed to combine those.

fun foo(a: Int, internal b: Int, protected c: Int, private d: Int) {}

Is it even possible to combine internal and protected like that in this case?

What issues would protected and internal pose? If it’s using default arguments, it should be okay.

As a class method, and you call it from an instance outside the module, you can only access the first parameter bar.foo(a = 3)

If you inherit it outside the module, you should be able to call foo(a = 3, c = 7) within your subclass.

If it’s within the module, then you can access the internal argument.

bar.foo(a = 3, b = 5)
foo(a = 3, b = 5, c = 7)

It might be confusing then if a parameter on the right has a broader visibility than one on the left:

fun foo(a: Int, private b: Int = 42, c: Int = 5) {}

The private on b would require at least the same restriction on the parameters to its right, namely c, and thus make the above declaration equivalent to:

private fun foo(a: Int, b: Int, c: Int) { ... }
private fun foo(a: Int, b: Int) = foo(a, 42, 5)
fun foo(a: Int) = foo(a, 42, 5)

I disagree. There is no reason that private would extend past the comma. It doesn’t when applied to primary constructor parameters:

class Foo(private val a :Int, val b: Int)

a is private, but b is not.

And unlike Java which allows multiple variable declarations like this:

private int a = 5, b = 7;

Kotlin requires normal properties to be declared separately. So I see no reason to expect that the visibility modifier would extend past the comma.

1 Like

For some reason I forgot that those parameters need to have default values.


One thing this might lead to confusion with is the constructor though.

class Foo(private val a: Int, val b: Int)

still would lead to a public constructor taking 2 arguments. In this case the private means that the property is private. This would lead to basically the same syntax with 2 different meanings. In one case the visibility modifier is creating additional overloads of the function and in case of the primary constructor it doesn’t.

My point was not about a syntax problem, but a semantic problem. What would you expect my function to be equivalent to in current Kotlin?

fun foo(a: Int, private b: Int = 42, c: Int = 5) {}

If you can’t know about b from the outside, and you want to provide c because it’s public, it seems the only way to call foo from outside would be with named parameters, or we would need 2 overloads of foo with 2 Int parameters, which AFAIK is not possible. Did I miss something here?

This is I believe, what @Wasabi375 also mentioned.

I’m confused. I’m not seeing the issue.

fun foo(a: Int, private b: Int = 42, c: Int = 5) {}

would be

fun foo(a:Int, c: Int = 5) = foo(a, 42, c)
private fun foo(a: Int, b: Int = 42, c: Int = 5) {}

EDIT: okay I just got the issue. If you were to call the function within the class, which one would it call. The private or the public function.
I suppose a way would be to keep visibility modified default paramters as the last few arguments ordered by permissibility.
Another way, though it might be confusing semantically, would be that the public function is only callable from outside the class. Whereas the private function is the only one callable from inside the class.

I think @icecubez reply was correct that it would be only one overload. And that would only be if they want to support the privateness of that parameter when calling from Java. Kotlin itself would not necessarily need it, but I could be wrong. I would have no problem if it only generated the overloads if you added @JvmOverloads.

No @Wasabi375 was talking about the fact in this proposal private controls whether the parameter can be passed, but in constructors it controls visibility of the generated property and those dual meanings could be confusing.

I think you are. As far as I know @JvmOverloads is only used to handle default parameters. Java itself does not have this concept so you create the overloads for those parameters.
I don’t think however there is a way to create functions multiple visibility modifiers on byte code level so I guess the overloads will be created in any case. It might be possible to define the overloads as inline (in case of protected and internal).

I thought that a simple case like this:

fun a(b: Int, c: Int = 5) { }

that Kotlin just generated the one method and simply called the full method passing the default, but I am wrong. That wouldn’t work in complex cases where the default value is more complex. I see that it generates a static synthetic bridge method that in Java looks like this:

   // $FF: synthetic method
   // $FF: bridge method
   public static void a$default(Foo var0, int var1, int var2, int var3, Object var4) {
      if ((var3 & 2) != 0) {
         var2 = 5;
      }

      var0.a(var1, var2);
   }

That seems to tell me that there is a performance penalty with default values for parameters.

Something like “the visibility level of a parameter should always be greater than all the ones after it” seems to fix the problem.

fun something(a: Int, internal b: Int = 0, protected c: Int = 0, private d: Int = 0)

Would generate, on the JVM:

fun something(a: Int)
internal fun something (a: Int, b: Int)
protected fun something (a: Int, b: Int, c: Int)
private fun something (a: Int, b: Int, c: Int, d: Int)

That seems fair and easy to understand. And their shouldn’t be any collision problem when generating the overloads, since the order is well-defined.