In the following code, where MyMap trivially implements Map by delegation to impl:
foo#host:/tmp$ cat Foo.kt
class MyMap <K, V> (val impl : Map <K, V>) : Map<K, V> by impl {
fun myGetValue (k: K) = impl.getValue(k)
}
fun main() {
val my_map = MyMap(mapOf('a' to 1, 'b' to 2).withDefault { 42 })
println(my_map.myGetValue('c')) // OK
println(my_map.getValue('c')) // ERROR
}
Why do I get the following error on the second println?
foo#host:/tmp$ /path/to/kotlinc Foo.kt
foo#host:/tmp$ /path/to/kotlin FooKt
42
Exception in thread "main" java.util.NoSuchElementException: Key c is missing in the map.
at kotlin.collections.MapsKt__MapWithDefaultKt.getOrImplicitDefaultNullable(MapWithDefault.kt:24)
at kotlin.collections.MapsKt__MapsKt.getValue(Maps.kt:344)
at FooKt.main(Foo.kt:8)
at FooKt.main(Foo.kt)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.jetbrains.kotlin.runner.AbstractRunner.run(runners.kt:64)
at org.jetbrains.kotlin.runner.Main.run(Main.kt:176)
at org.jetbrains.kotlin.runner.Main.main(Main.kt:186)
foo#bigdev:/tmp$
Update: The compiler and runtime version outputs are:
foo#host:/tmp$ kotlinc -version
info: kotlinc-jvm 1.6.10 (JRE 17.0.1+12-LTS)
foo#host:/tmp$ kotlin -version
Kotlin version 1.6.10-release-923 (JRE 17.0.1+12-LTS)
foo#host:/tmp$ javac -version
javac 17.0.1
foo#host:/tmp$ java -version
openjdk version "17.0.1" 2021-10-19 LTS
OpenJDK Runtime Environment Corretto-17.0.1.12.1 (build 17.0.1+12-LTS)
OpenJDK 64-Bit Server VM Corretto-17.0.1.12.1 (build 17.0.1+12-LTS, mixed mode, sharing)
This is occurring because of the slightly unexpected way in which withDefault is implemented. The wrapper that withDefault produces doesn't override getValue() as this is impossible because getValue() is an extension function. So unfortunately, what we have instead is a classic OOP anti-pattern: getValue() does an is check to see if it's being called on the internal MapWithDefault interface, and only uses the default value if that is the case. I don't see any way they could have avoided this situation without breaking the Map contract.
myGetValue calls getValue on the underlying delegate, which is a MapWithDefault, so it works fine.
getValue called on your MyMap instance will fail the internal is MapWithDefault check because MyMap is not a MapWithDefault, even though its delegate is. The delegates other types are not propagated up to the class that delegates to it, which makes sense. Like if we delegated to a MutableMap, we might want the class to be considered only a read-only Map.
Though I would have expected your code to work to be honest, this could be bug but we'd have to look at the produced bytecode.
In the documentation it says (emphasis mine):
This implicit default value is used when the original map doesn't contain a value for the key specified and a value is obtained with Map.getValue function, for example when properties are delegated to the map.
The conflict of "contracts" comes from the actual Map interface, which says:
Returns the value corresponding to the given [key], or null if such a key is not present in the map.
The maps default contract must fulfill this, so it can "only" return null when a key is non-existent.
I have found one discussion about this in the Kotlin forums.
Related
I tried to implement some type classes from Haskell but confronted the issue that is probably a bug in the Kotlin compiler.
interface Semigroup<Instance> {
infix fun Instance.assocOp(oother: Instance): Instance
}
inline fun <reified T: Semigroup<T>> Iterable<T>.concat() = this.reduce<T, T> { acc: T, t: T -> acc.assocOp(t) }
The error message is "Expected parameter of type T".
IDEA suggests to "Change type from 'T' to 'T'" (does nothing).
I expect acc to belong to the type T mentioned in generics. But because of some reason compiler tries to find some other type T. I tried to
specify the type explicitly/implicitly
build ignoring IDEA message
change used version of Kotlin compiler (I have tried 1.4.20, 1.4.10, 1.3.72).
Nothing worked.
I suppose that writing the function without reduce (manually) may help to deal with it. Also, writing java code doing the same may help to mitigate the problem. But these solutions are only workarounds for the problem. Is the issue my fault or the compiler bug?
The compiler error clearly is not helpful here. However, it is correct that the code should not compile IMO.
You're defining the method assocOp as a member extension function. The extension applies to any type T, but it's a member of the interface Semigroup<T>.
To call that extension, you need both a receiver or type T and a receiver of type Semigroup<T> (acting as a context).
In your case, the type T both plays the role of the generic type parameter and of the Semigroup<T>, but you still need to have 2 "receivers" for your extension, even if both are the same instance.
Maybe try this:
inline fun <reified T : Semigroup<T>> Iterable<T>.concat(): T =
reduce<T, T> { t1: T, t2: T -> with(t1) { t1.assocOp(t2) } }
The with(t1) { ... } provides a context of type Semigroup<T>, while the t1 used in t1.assocOp(t2) acts as the T receiver.
I am looking at this https://docs.gradle.org/current/samples/sample_building_kotlin_libraries.html
to build a Kotlin jar library. But I am wondering -- if consumer code of the library is also using Kotlin, can it have all the benefits like accessing to local Kotlin code? For example:
Nullability check
Suspend keyword restriction
Named parameters
...
I figured out the answer is YES as long as the jar is built with source files.
suspend
The suspend keyword changes the descriptor of the functions to have an interface, Continuation<ReturnType> at the end of the parameter list. So if you have
suspend fun foo(i: Int, l: Long): String in kotlin, it gets compiled and decompiled to Java:
public final Object foo(int i, long l, Continuation<? super String> $completion). This is why some libraries had Java friendly functions that wraps suspend function call in runBlocking.
Null Checks
This works across languages, when kotlin compiles a function or expression call that specifies a non-null value, it adds null checks to the code. For example:
fun bar(s1: String, s2: String) adds these check in the method body:
Intrinsics.checkParameterIsNotNull((Object)s1, "s1");
Intrinsics.checkParameterIsNotNull((Object)s2, "s2");
And throws an exception when it is null.
I want to convert integers to bigDecimals. However, it seems that IntelliJ'Idea sees twice the definition for the toBigDecimal().
when trying on the REPL I get this error:
12.toBigDecimal()
error: overload resolution ambiguity:
#SinceKotlin #InlineOnly public inline fun Int.toBigDecimal(): BigDecimal defined in kotlin
#SinceKotlin #InlineOnly public inline fun Int.toBigDecimal(): BigDecimal defined in kotlin
Meanwhile it is fine using Strings:
"12".toBigDecimal()
res1: java.math.BigDecimal = 12
I am using Kotlin version 1.3.61 (JRE 1.8.0_232-b09)
I am very confused and I don't know waht to look after. any pointers?
I have just experienced the same issue and managed to solve it.
Just right click on the error and select ignore in compile and it will compile and run fine.
I am facing some very basic problem (that never faced in java before) and might be due my lack of knowledge in Kotlin.
I am currently trying to read a YML file. So Im doing it in this way:
private val factory = YamlConfigurationFactory(LinkedHashMap::class.java, validator, objectMapper, "dw")
Best on Dropwizard guide for configurations.
https://www.dropwizard.io/1.3.12/docs/manual/testing.html
So later in my function I do this"
val yml = File(Paths.get("config.yml").toUri())
var keyValues = factory.build(yml)
When using my debugger I can see there is a Map with key->values, just as it should be.
now when I do keyValues.get("my-key")
type inference failed. the value of the type parameter k should be mentioned in input types
Tried this but no luck
var keyValues = LinkedHashMap<String, Any>()
keyValues = factory.build(yml)
The YamlConfigurationFactory requires a class to map to, but I dont know if there is a more direct way to specify a Kotlin class than with the current solution +.kotlin, like
LinkedHashMap::class.java.kotlin
Here it also throws an error.
Ideas?
Well, this is a typical problem with JVM generics. Class<LinkedHashMap> carries no info on what are the actual types of its keys and values, so the keyValues variable always ends up with the type LinkedHashMap<*, *> simply because it can't be checked at compile time. There are two ways around this:
Unsafe Cast
This is how you would deal with the problem in standard Java: just cast the LinkedHashMap<*, *> to LinkedHashMap<String, Any> (or whatever is the actual expected type). This produces a warning because the compiler can't verify the cast is safe, but it is also generally known such situations are often unavoidable when dealing with JVM generics and serialisation.
YamlConfigurationFactory(LinkedHashMap::class.java, ...) as LinkedHashMap<String, Any>
Type Inference Magic
When using Kotlin, you can avoid the cast by actually creating instance of Class<LinkedHashMap<String, Any>> explicitly. Of course, since this is still JVM, you lose all the type info at runtime, but it should be enough to tell the type inference engine what your result should be. However, you'll need a special helper method for this (or at least I haven't found a simpler solution yet), but that method needs to be declared just once somewhere in your project:
inline fun <reified T> classOf(): Class<T> = T::class.java
...
val factory = YamlConfigurationFactory(classOf<LinkedHashMap<String, Any>>(), ...)
Using this "hack", you'll get an instance of LinkedHashMap directly, however, always remember that this is just extra info for the type inference engine but effectively it just hides the unsafe cast. Also, you can't use this if the type is not known at compile type (reified).
I came from the C# background and I know how to implement this in C#, but I'm struggling with Kotlin.
I've got 2 extension functions:
fun <T> Foo<T>.myFunction(func: () -> Unit): Foo<T>
and
fun <T> Foo<T>.myFunction(func: () -> Foo<T>): Foo<T>
Clearly, the return type of func is different in both functions. The first function executes it and returns this, the second executes func and returns the result of func.
But it gives me an error:
"Platform declaration clash: The following declarations have the same JVM signature".
How to implement this correctly in Kotlin?
Your functions have a conflicting signature within the JVM due to type erasure (the internal Function0<T> class being used to represent the function parameters); and you can fix this by giving each of them a JVM specific name. From Kotlin you would still access them by the original name, but from Java or internally another name is actually used. Simply use the #JvmName annotation on the alternative versions:
fun <T> Foo<T>.myFunction(func: () -> Unit): Foo<T>
#JvmName("myfunctionWithFoo")
fun <T> Foo<T>.myFunction(func: () -> Foo<T>): Foo<T>
On the JVM, we have to contend with type erasure. Meaning essentially that the types (T in this case) are thrown away in the compiled bytecode and that required checks are only done at compile time. Given that, you have to look at your function declaration with that in mind.
Kotlin will define your function argument as a Function0 in both cases. Because the types are erased, () -> Unit and () -> Foo<T> both look the same in the bytecode. We can prove this out by decompiling the code you've provided (I renamed one of these myFunction2 to get this to work):
public final class com/ginsberg/KotlinStuffKt {
public final static myFunction(Lcom/ginsberg/Foo;Lkotlin/jvm/functions/Function0;)Lcom/ginsberg/Foo;
public final static myFunction2(Lcom/ginsberg/Foo;Lkotlin/jvm/functions/Function0;)Lcom/ginsberg/Foo;
}
This is what the Kotlin compiler is generating (it does more, but I have removed the non-essential parts from this example). As you can see, our types are gone thanks to type erasure. And if we undo my change (myFunction2 becomes myFunction), there's no way at all to tell these apart. This is what the compiler is complaining about - if you erase the types the JVM can't tell these functions apart.