Pass-through varargs


#1

Consider the following:

fun format(msg: String, vararg args: Any?) {
String.format(msg, args)

}

The signature of function format is the same as String.format. But the following fails.

format("Number is %d", 2)
java.util.IllegalFormatConversionException: d != [Ljava.lang.Object;
at java.util.Formatter$FormatSpecifier.failConversion(Formatter.java:4302)
at java.util.Formatter$FormatSpecifier.printInteger(Formatter.java:2793)
at java.util.Formatter$FormatSpecifier.print(Formatter.java:2747)
at java.util.Formatter.format(Formatter.java:2520)
at java.util.Formatter.format(Formatter.java:2455)
at java.lang.String.format(String.java:2940)
at Line12.format(line12.kts:2)

I know spreading args when passing to String.format works but the expected behavior is that the above should work, or at the very least the compiler / IDE should warn about this usage.


#2

Well you are incorrect, you have to use the spread operator


#3

So you’re saying the compiler / linter SHOULD let a type mismatch pass through? And only at runtime should you be able to realize that varargs don’t pass through without spreading?


#4

No, the problem is not in Kotlin, it is in the fact that String.format is fundamentally type unsafe (GCC/CLang have options for C/C++ to mark arguments as format strings to be able to do type checking). The type is specified in the string parameter and without additional metadata the compiler can’t do anything about that at compile time. The concept of spreading is safer than the Java implementation as there is a single parameter type instead of automatic vararg deduction. (Ever tried to have a single array be the variable argument of a vararg object[] in Java? You will have to do manual wrapping into a container array.)


#5

In what way is it a type mismatch? Format takes a vararg array of objects. Args in this case is an array, which is an object.

Your assumption that varargs should always be passed through using spread is false. With format it is less likely that you want to pass the array, but sometimes you might. Perhaps I am doing some debug logging and want to print the identity of the array (i.e. the hashcode) using %s and I later do the same thing on another array variable because I want to look at the log to see if that other variable refers to the same array instance or a different one.

In the general case of varargs there is no single correct answer as to whether you want to pass an array as a single parameter or pass the elements of the array as individual parameters. The fact that the array itself was created from varargs argument doesn’t change that.

Kotlin, unlike Java, at least gives you the spread operator so you can choose which of the 2 you mean. Your argument seems to be that Kotlin should take away that choice from you.


#6

That seems reasonable. Can you point me to how the C options you mention work? I haven’t been in the C world for a while and Google didn’t find what I was looking for.

So if you’re saying the issue is caused by String.format being fundamentally type-unsafe, what is the idiomatic Kotlin version of format?

Having said that, I would still expect some linting around this at the very least such as with support annotations.


#7

Your conclusion might be correct, better explained by pdvrieze probably (though I still have my doubts and have asked for more info), but I disagree with your rationale. Printing out an object identity is not a good enough reason for not passing varargs through. That is not possible for any number of other types, what if you have to want to see if two objects that implement toString() are references to the same instance which debugging.

Furthermore, you refer to the varargs as equivalent to an array but Kotlin itself discourages this. You can’t pass an arrayOf(1, 2, 3) as a vararg Int parameter, but inside the function, suddenly the developer is expected to treat it as exactly equivalent to an array. I should point out that I understand the rationale behind this part and agree with it, I’m only contesting that vararg Intshould be consistently treated as vararg Int which is not equal to Array<Int>. This would be similar to a distinction between Array<Int> and IntArray if I understand correctly.

There might be valid reasons for spreading varargs, but this print out array hashcodes is not one of them.


#8

For gcc, it is the format attribute described here: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes

This is a gcc specific extension (although clang may well accept it as well)


#9

My point there was merely it is not true that the compiler should always assume that when passing an array to format that it should be spread. Usually you probably want it to be spread, but it would be wrong to assume that is always the case.

And debugging where you are checking to see if 2 objects are the same object by looking at their toString representation which was the default toString is not hypothetical, I have done it. In that case it had nothing to do with arrays or format. I was just checking at different breakpoints and looking in the debugger variables window.

You can pass the array you just have to use the spread operator. Kotlin doesn’t discourage you, it just wants you to specify what you want to have happen. That is a good thing and I still have not heard any reasoning why you think Kotlin knows better and should take away that choice from you.

Inside the function it is treated exactly as an array. There is no distinction inside the function. Varargs is only about the caller of the function

Java doesn’t give you control. In some cases it assumes spread the array and in others it assumes it should pass the array as a single argument.

Consider a method foo that takes a single var arg parameter. In the case where we pass an array to it in java:

foo (anArray)

Java assumes that you want the array to be spread. If you didn’t want it to be spread Java doesn’t give you a way to specify that and your only solution is to make another array and put this array into it and pass that. Kotlin gives you the choice. By default it passes the array as a single value and if you want it to be spread you use the spread operator. If it passed the array as a spread how would you tell it to not spread it? Create an unspread operator? Force you to wrap it in another array like Java? I think Kotlin’s answer is best.

But take another case:

foo (anArray, aSingleValue)

In this case Java assumes the opposite and will pass the array as a single value and not spread the array. The only way to pass all the elements of the array and the additional value is to build a new array yourself and pass that. Kotlin gives you the choice to spread or not to spread.

To me your argument basically is that the default should have been to spread arrays passed to varargs and the * operator would be the unspread operator. It’s a little late to reverse direction and I think it is more logical that bar(array) always means passing the array as a single parameter whether the function parameter is varargs or not and spreading is the additional operator.

[quote=“saadfarooq, post:7, topic:2431”]
There might be valid reasons for spreading varargs, but this print out array hashcodes is not one of them.[/quote]

I assume you meant Not spreading here. Kotlin had to treat all varargs the same. It can’t do it one way for format and a different way for other varargs. That example was just to say that even for format you might want to pass an array without spreading it.

The bottom line is that when passing an array to a vararg sometimes you want to pass the array itself and sometimes you want to pass the elements of the array. Kotlin gives you a way to specify. The default is to treat the array the same way as if you were passing it to a non varargs function.

I see nothing that should be changed here.


Kotlin or Swift? (isn't a flame war, its about opinion)
#10

That looks nice. Would be great to have something like that in Kotlin.


#11

No, my argument is that a parameter vararg args: Int? should be distinct from args: Array<Int> and the distinction between them should be analogous to the distinction between Array<Int> and IntArray i.e. they should be functionally equivalent but not the same type.

I realize this presents the problem of passing the vararg array instance as a vararg parameter to another function, but again the IntArray analog should work, i.e. you can do args.toTypedArray() to explicitly get the array.

A close analog to this situation would be type annotations. For example, if you have a @StringDef and methods that take that @StringDef annotated String as parameter, you should be able to do the former and not have to do the latter.

@StringDef( { STRING_1, STRING_2, STRING_3})
public interface @MyString {}


void passOn(@MyString String aString) {
    takeMyString(aString);
}

void takeMyString(@MyString String sameString) {
    switch(sameString) {
        case STRING_1:
            passOn(STRING_1);
            break;
        case STRING_2:
            passOn(STRING_2);
            break;
        case STRING_3:
            passOn(STRING_3);
            break;
    }
}

Semantically, this is roughly what Kotlin is asking to do here with the vararg, i.e. you provided some additional type information for the function parameter but once inside the function, I will forget that type information and you have to convert (in this case spread) again if you want to pass it to another function that takes a vararg. Though the operation (spreading) is obviously not as verbose as the operation (switch-case) in @StringDef example, it’s still an operation happening somewhere.
Ultimately, the philosophical argument here is that more type information is better and that type information should be not lose significance unless explicitly told to (@MyString String should be treated as different from String unless explicitly changed and vararg Int should to treated different from Array<Int> unless explicitly change). In fact, as suggested in the other answer, even more type information in varargs would be better following the same philosophy.

This still leaves us with the case of trying to match arrays by memory allocation addresses by printing out arrays, but as I’ve said before there’s any number of cases where toString() does not print out hashcodes and it seems weird to me to base a semantics decision on this one use case. Especially since you would still not use that ability and could just call toString() explicitly instead of passing an object for a string format specifier and have it do so implicitly. Again, explicit type conversion is better than implicit type conversion.


#12

But as of yet have not presented any justification why they should be treated any differently inside the function. So far all I have heard is that they should be treated differently because they should be treated differently.

Fundamentally they would be the exact same Java type, but that of course is not enough to say that they have to be treated the same since nullable and non-nullable object references are also the same thing in Java and the distinction is only in the Kotlin type system.

Why would we need it to be treated any different? The only place you have said there is a difference is that when passing them to another vararg function the vararg array should be spread and the non-vararg array is passed as an array.

Let me try to explain why I think treating them is bad and why Kotlin’s approach is the most consistent. Imagine I have parameter value that was vararg and another variable that was an array and consider 4 different calls:

normalFunction(myVarArgVariable);
normalFunction(myArrayVariable);
normalFunction(myVarArgVariable, anotherValue);
normalFunction(myArrayVariable, anotherValue);
varArgFunction(myVarArgVariable);
varArgFunction(myArrayVariable);
varArgFunction(myVarArgVariable, anotherValue);
varArgFunction(myArrayVariable, anotherValue);

And the question is how is the array or vararg treated in each of the 8 cases:

In Java we have for cases 5 and 6 the parameter is spread and in all other cases the array is passed as a single value. And we also have the fact that in all these cases if you want the opposite behavior it is up to you to wrap things in another array.

In Kotlin, it is dead simpler. In all those cases the array is passed as a single value and not spread. If you want the opposite behavior you simply use the spread operator.

So your proposal is that for cases 1, 3, 5, and 7 the array is spread and to get the opposite behavior you have to wrap in an array again like Java.

The big problem with this and with Java is that if I see:

foo(myVarArgParameter)

I don’t know how the parameter is passed to foo unless I go look at foo. I have to go see if foo is vararg or not. With the way kotlin is now I know that no matter what that array is passed as a single parameter.

And I don’t see vararg-ness as type information. The only real purpose for declaring something as vararg is to provide a more convenient way of specifying the contents not that it is a different type. As I shown trying to treat it as a different type for purposes of special rules for argument passing comlicates things for no actual gain.

You seem fixated by this example. I am not basing my case on this one use case

In general, when passing an array to a vararg parameter there are cases where you want it to be spread and cases when you don’t. This example only served one purpose, which was to say that this is even true for String.format. All I was saying is that format is not an exception to the rule that sometimes you want to spread and sometimes you don’t.

Given that sometimes you want to spread an array and sometimes you don’t, Kotlin provides the simplest, most consistent approach to this which is to give the caller control of which they want to have happen. If you don’t add the spread operator the parameter is handled the exact same way no matter what function you are passing it to and there are no complicated rules and special cases.