1 fun main() {
2 val listOfWords = arrayOf("one", "two", "three")
3
4 // Test1
5 val test1 = listOfWords[1].toCharArray()
6 test1.shuffle()
7 String(test1)
8
9 // Test2
10 val test2 = listOfWords[1].toCharArray().shuffle()
11 String(test2) // Problem here: String() doesn't like test2
12 }
Why does line 11 fail?
Or, maybe a better question is: why is String() happy with test1, on line 7?
https://pl.kotl.in/y62nvnNfa
This is long but I hope it explains a few things, and helps you work out how to solve these errors when they come up!
Here's the error you get:
None of the following functions can be called with the arguments supplied:
public inline fun String(stringBuffer: StringBuffer): String defined in kotlin.text
public inline fun String(stringBuilder: StringBuilder): String defined in kotlin.text
public inline fun String(bytes: ByteArray): String defined in kotlin.text
public inline fun String(chars: CharArray): String defined in kotlin.text
Those are the possible one-argument calls to String() and it's saying the arguments you're supplying (just test2 in this case) don't match any of those signatures. It's the wrong type.
Since you're working with CharArrays and that's one of the accepted arguments, you probably think test2 is one, but it's not. That's what you need to investigate. Like you say, test1 is fine - so what's the difference here? What's different about the way you're creating those two values, that they'd end up being different types?
The difference is with test2 you're calling shuffle() on it. You probably expected that to return the newly shuffled array, but since test2 apparently isn't a CharArray, then seems like something else is going on, right? Better check what shuffle actually does:
fun CharArray.shuffle()
Randomly shuffles elements in this array in-place.
Two things there. One - it says it shuffles in-place meaning it doesn't return a new copy of the array with the contents shuffled, it just moves things around in the given array object. You see this a lot, e.g. sort (in-place) vs sorted (produces a new sorted copy, the original is untouched) and it's important to be aware that there are often two versions of this kind of thing!
The second thing is that the function signature up there has no return type. So it's really
fun CharArray.shuffle(): Unit
Which is another sign that it doesn't produce anything, it just modifies something. And it means that it doesn't return a value (except Unit, which really represents no value like void in Java). So when you create test2, calling shuffle on an array, the result is Unit, nothing, nada. And that's why you can't pass it to the String constructor.
There actually is a shuffled() function, but it's only for Iterables. So you could do this:
// converting to a `List` so you can run `shuffled` on it
val test2 = listOfWords[1].toCharArray().toList().shuffled().toCharArray()
but you might as well skip that intermediate array:
val test2 = listOfWords[1].toList().shuffled().toCharArray()
Related
I would like to make plus mean something else, than addition. For example, creation of lazy expressions for computational graph. Unfortunately, class extensions cant override member functions. The following code will print 3:
operator fun Int.plus(other: Int) = listOf(this, other)
fun main() {
println( 1 + 2 )
}
Is is possible to force overriding?
No it is not possible. 1 + 2 is lowered into 1.plus(2), and there is a well defined order in how the compiler finds an appropriate plus method. Specification:
If a call is correct, for a callable f with an explicit receiver e
of type T the following sets are analyzed (in the given order):
Non-extension member callables named f of type T;
Extension callables named f, whose receiver type U conforms to type T, in the current scope and its upwards-linked scopes, ordered
by the size of the scope (smallest first), excluding the package
scope;
[...]
[...]
When analyzing these sets, the first set which contains any
applicable callable is picked for c-level partition, which gives us
the resulting overload candidate set.
So the plus method that is declared in Int is always found first, and the search stops there. Any extension you define will be ignored.
Hypothetically, if the built-in Int.plus is an implicitly imported extension function, then your code would have worked! Implicitly imported extensions are #6 on that list :)
My workaround for this situation is to use the "declare functions with almost any name by adding backticks" feature:
infix fun Int.`+`(other: Int) = listOf(this, other)
fun main() {
println( 1 `+` 2 )
}
This wouldn't work for some names that have reserved characters like square brackets, angle brackets, slashes, and dot (not an exhaustive list).
Let's say I have the following class:
data class Foo(var name: String) {
operator fun plus(foo: Foo): Foo {
name += foo.name
return this
}
}
Which is then used like this:
val foo1 = Foo("1")
val foo2 = Foo("2")
val foo3 = Foo("3")
foo1+foo2+foo3
println(foo1.name) // 123
Now, what if I wanted different behavior depending on whether the operations are chained like this:
foo1+foo2+foo3
Or like this:
(foo1+foo2)+foo3
In both cases foo1's name would be 123, but let's say that in the second case I would want foo1's name to be (12)3.
Is there a way to add a condition to the plus function, which checks whether the foo that it is called on originates from within parentheses/has a higher precedence or not.
No, that is not possible, because that makes no sense tbh. The compiler will just resolve the order of operations, brackets just indicate that 1+2 should resolve first and the result should be added to 3. There is no concept of brackets anymore in that result, you just have the outcome.
What is confusing you is that you are abusing the plus function to do something people wouldn't expect. You should not use the plus function to mutate the object it is called upon, this is not expected behaviour. Users will expect the plus function to return a new object not a mutation of the left or right operand.
In your case:
operator fun plus(foo: Foo): Foo {
return Foo(name += foo.name)
}
Don't do something different lest you want other people to be really confused. Fyi plusAssign is a mutating function, but still wouldn't allow you to do what you want. To achieve that you'd probably need to write your own parser and parse the operands and operators yourself.
val list1 = listOf(1,2,3,4,5,6,7,8,9)
val list2 = listOf(1..9)
/*
my result1 is valid but result2 gives error
*/
val result1= list1.filter{i -> i>2}
val result2 = list2.filter{i -> i>2}
listOf(1, 2, 3, 4, 5, 6, 7, 8, 9)
is a list of nine numbers — specifically, Ints.
However, 1..9 is a range object — specifically, an IntRange. So:
listOf(1..9)
is a list that contains a single object.
list1.filter{ i -> i > 2 }
is valid, because filter() applies its lambda to each Int in the list — and the > operator is defined between two Ints. However:
list2.filter{ i -> i > 2 }
is not valid, because the lambda is applied to the IntRange, and you can't compare an IntRange to an Int.
Your IDE and compiler should give you several clues about this. First, if you hover over list1 and list2 you'll see the types that the compiler infers for them: List<Int> and List<IntRange>, respectively.
And not only does the IDE underline { i -> i > 2 }, it also shows the > in red. Hovering over that shows the error ‘Unresolved reference. None of the following candidates is applicable because of receiver type mismatch…’ That's a bit cryptic, but you should get to know it, because it's quite common and very useful. It's telling you that you're trying to call a function/method/operator that's not defined for the type(s) you have. In this case, > (which is equivalent to compareTo()) is defined for two Ints, two Longs, two Strings, etc., but not for an IntRange and an Int.
So the compiler doesn't know what return type it should have, and temporarily assumes Unit — but filter() needs a lambda returning a Boolean, which is why the whole lambda is underlined with a ‘Type mismatch’ error.
As Tenfour04 says, you can convert a range into a list if you need to (using the toList() method). But in practice, that's not needed very often because there's usually a better approach. (A range takes much less memory than most lists, and is therefore quicker to iterate through; and it implements Iterable so you can already do most list-type things with it anyway.)
As mentioned in the code provided. My question is if there is any advantage by mentioning the data type early on when I am just giving the variable it's value. As said in line 2 it can understand that it is int type.
val a: Int = 1 // immediate assignment
val b = 2 // `Int` type is inferred
val c: Int // Type required when no initializer is provided.
A few reasons you might want to specify a type explicitly:
You want a supertype of the value (perhaps so you can to reassign it later), e.g.:
var myList: List<String> = ArrayList<String>()
You want to protect against changes to other code (especially if it's outside your control), e.g.:
val x: MustBeThisType = SomeLibrary.getValue()
(That would give an error if SomeLibrary.getValue() ever changes to returns something other than MustBeThisType or a subtype.)
You want to avoid an explicit numeric conversion, e.g.:
val x: Long = 2
instead of:
val x = 2.toLong()
You want to make it very clear to someone reading your code (especially if that might not be in an IDE).
As Michael says, you may need to specify the nullability of types returned from Java, e.g.:
val x: String = someJavaClass.getAString() // Never returns null
None of those is particularly common in my experience, though.
I want to write an extension function which will be available on any type and accept parameter of the same type or subtype, but not a completely different type.
I tried naive approach but it didn't work:
fun <T> T.f(x: T) {
}
fun main(args: Array<String>) {
"1".f("1") // ok
"1".f(1) // should be error
}
It seems that compiler just uses Any for T. I want T to be fixed to receiver type.
The only way to do it requires telling the compiler what you want.
fun <T> T.f(x: T) {
}
In order to use it, you have to tell Kotlin what you want the type to be.
"1".f<String>("2") // Okay
"1".f(2) // Okay (see voddan's answer for a good explanation)
"1".f<String>(2) // Fails because 2 isn't a String
"1".f<Int>(2) // Fails because "1" isn't an Int
When you call fun <T> T.f(x: T) {} like "1".f(1), the compiler looks for a common super-type of String and Int, which is Any. Then it decides that T is Any, and issues no error. The only way to influence this process is to specify T explicitly: "1".f<String>(1)
Since all the checks are performed by the compiler, the issue has nothing to do with type erasure.
Your issue is like saying "John is 3 years older than Carl, and Carl is 3 years younger than John" ... you still don't know either of their ages without more information. That's the type of evidence you gave the compiler and then you expected it to guess correctly. The only truth you can get from that information is that John is at least 3 years old and Carl is at least 1 day old.
And this type of assumption is just like the compiler finding the common upper bounds of Any. It had two strong literal types to chose from and no ability to vary either. How would it decide if the Int or String is more important, and at the same time you told it that any T with upper bounds of Any? was valid given your type specification. So the safe answer is to see if both literals could meet the criteria of T: Any? and of course they do, they both have ancestors of Any. The compiler met all of your criteria, even if you didn't want it to.
If you had tie-breaking criteria, this would work out differently. For example, if you had a return type of T and a variable of type String receiving the value, then that would influence the decision of Type inference. This for example produces an error:
fun <T: Any> T.f2(x: T): T = x
val something: String = "1".f2(1) // ERROR
Because now the type T is anchored by the "left side" of the expression expecting String without any doubt.
There is also the possibility that this could also be an type inference issue that is not intended, check issues reported in YouTrack or add your own to get a definite answer from the compiler team. I added a feature request as KT-13138 for anchoring a specific type parameter to see how the team responds.
You can fix T to the receiver type by making f an extension property that returns an invokable object:
val <T> T.f: (T) -> Unit
get() = { x -> }
fun main(vararg args: String) {
"1".f("1") // will be OK once KT-10364 is resolved
"1".f(1) // error: The integer literal does not conform to the expected type String
}
Unfortunately "1".f("1") currently causes an error: "Type mismatch: inferred type is String but T was expected". This is a compiler issue. See KT-10364. See also KT-13139. You can vote on and/or watch the issues for updates. Until this is fixed you can still do the following:
"1".f.invoke("1")
/* or */
("1".f)("1")
/* or */
val f = "1".f
f("1")