Does anybody find Kotlin's Type Casting disgusting?

Hello everyone,

I admit that Kotlin should be the best JVM language ever, but there is always something annoying about this supposed-to-be-great language. To me, one of the most frustrating ‘feature’ of Kotlin is the Type Casting system, especially in function parameters: Why is a Float not a Double, or an Int not a Long?

There are some functions that requires Double as their parameters and for every single argument I have to add a conversion “toDouble()” for every single values though all my values are Float, which is really annoying. I do not say it is impossible, it is possible but annoying. It maybe a good habit to do so, but sometimes I only want to write a quick, dirty program for testing my ideas. Can’t you make it warning instead of error?

Anyone seeing the Google Trend for Kotlin may wonder why Kotlin’s trend has always been neither increasing nor decreasing. It’s been just fluctuating. It went up around the time when Google announced its support for Android and went down almost instantly and keeps fluctuating until now. I think it is because of these conservative features that Kotlin cannot be more popular.

Please do something about it. I think Kotlin is very promising but it will end soon if things continue like this.

3 Likes

I think the team at JetBrains made this type casting deliberately and there are many good reasons for and against it. I personally like the fact that there is no implicit conversion between Int and Long, because you know when you loose precision.

As to your problem of having to write toDouble(), etc everywhere: You could simply use something like this

val Number.d = this.toDouble()

That way you can just write

val double1: Double = 5.d 
val double2: Double = someNumber.d
9 Likes

Simply write a wrapper accepting a Float that does the casting for you. Annoying if you have to write lots of wrappers, but it is a chore you only have to do once.

2 Likes

You are talking about automatic type conversion, not type casting.

Why is a Float not a Double, or an Int not a Long?

Why is a Char not a String, or an Array not a List?

2 Likes

The reason why primitive types are not implicitely casted is explained in the documentation.

9 Likes

I have a completely opposite experience. Java automatic cast of int to double and vice versa is a source for a lot of nasty errors which are hard to find. The simplest example is this one: 1*1.1. Java uses the left hand argument for multiplication to infer the type of the result and rounds right hand argument producing 1 instead of 1.1. The case of automatic casts ints to longs is also not so obvious. For example we have two large numbers a: Int and b: Long then we calculate c = a*b, the result of the operation will depend on the order of arguments. In one case it would produce Long, and in another case it would produce Int and could cause overflow. There is a lot of other examples.

So, I am very happy that kotlin finally completely rejected C-style implicit type conversion and it is wrong to reintroduce it.

As for implicit type conversions, you can use Groovy, it automatically translates all numbers into BigDecimals and has customizable implicit type conversions. I worked with it for some time before switching the most of my code base to kotlin and I can say, that it is good for simple scripts, but for something more complex, implicit conversion is a huge problem.

17 Likes

You seem to have a wrong understanding of Java. 1 * 1.1 returns a double (1.1) just as 1.1 * 1.

You’re either thinking about the *= operator (1 *= 1.1 is indeed 1 because it needs to stay an int) or you’re thinking about cases where you’re partially doing Integer calculations like 1/2*1.1 (which will be 0.0 instead of 0.55 if you calculate it in this order)u

I’ve oversimplified. And I did not get this particular error for a long time because I always use d literals in java to avoid it. But the example you presented (1/2*1.1) is the best proof of my point.

1 Like

I think there should be a clear distinction between type casting (i.e. o as Thing) and type conversion (i.e. 10 as an Int → 10 as a float). In the latter, they might abstractly represent the same thing, but they are two completely different data points. The C-style type casting tried to wear far too many hats for my liking. Even without unsafe casts being a thing, the old syntax is one I’d rather see go the way of it.

That said, converting Ints to floating points is a simple reality of coding and when the “least-ugly” solution to the extremely common case is distance( x + 0f, y + 0f) then I think there’s room for improvement.

Also, personally, I don’t get the logic behind disabling implicit number widening conversions for things of the same kind (i.e. Short to Int to Long). It’s not like data is being lost the way it is when converting Int to Float and compiler logic could easily handle the case of nullible equality. Just because java.lang.Long(0) != java.lang.Int(0) does not mean (0 as Long?) != (0 as Int?) has to be true. Is “Long?” really “implicitly a boxed Long” and not a “Nullible Long”? It’s your language, isn’t it?

3 Likes

I think the problem here is Java interop. Internally a==b is going to be a call to a.equals(b) which, at the JVM level, doesn’t behave as expected if a is an Int and b is a Long. It sounds like what you’re suggesting is to add some kind of compiler magic, but that falls apart if the references are stored in variables of type Any:

val a : Any? = 1
val b : Any? = 1L
if( a==b ) {
   // ...never executed...
}

The problem is if Long? and Int? are not handled as boxed longs, it breaks seamless interop with Java code in the same app. If Java had included widening conversions in the implementation of boxed types, this would not be an issue, but it’s too late to change that now as it would break legacy code.

I think thing .toInt() and .toLong() look ugly too, but I don’t have any other good ideas about how to make this work consistently without breaking interop (and as an Android developer, interop is super important for me—without it, I would have to go back to just using Java—yuck!)

1 Like

Well, actually, it is a bit bad in this case if you are not familiar with it, but when you are (if you code a lot in C/C++), you will just naturally write the code in the correct order. I think toInt()/toDouble()… are great names but I find it terrible to strictly forbid type automatic conversion. It is better if you make them return a warnings instead of errors.

My current job is testing and we just discussed whether we should try Kotlin for testing, but I really really do not like the fact that I have to but every “.0” or “f” accordingly to our tests. Writing a wrapper is fine for 1 or 2 functions but we have at the very least hundreds of them…

2 Likes

I am not sure how many people do that.

I agree that when testing this is annoying. And I would like to see Kotlin have weaker typed number literals so that I can do this:

fun foo(double: Double) { ... }
foo(1)

but not this

val i: Int = 10
foo(i)   // compiler error

I might even be fine with something like this

val n = 10
foo(n)

but than I would argue that n should be of type Double and you should no longer be able to do

val i: Int = n     // compiler error
val i2: Int = n.toInt()

I’m delighted that Kotlin doesn’t have implicit type conversion. I’ve made plenty of subtle errors in the past. Extension functions can make explicit conversion terse.
Whether or not this language choice determines Kotlin’s popularity, as you claim, who knows?

4 Likes

It has helped me many times to stear by bugs. for example using RandomAccessFile.seek API with files larger than 2G

I felt kind of sad when I did this code in kotlin native

val pId = glCreateProgram!!()

glAttachShader!!(pId.toUInt(), vsId.toUInt())
glAttachShader!!(pId.toUInt(), fsId.toUInt())
glLinkProgram!!(pId)

means I put “!!” in all the lines related to shader programs in OpenGL,
the code itself looks bad , same for toInt()

1 Like

Kotlin types are strong. It means no default conversion. You should specify each one. Not because it’s great. Just because it’s strong.
It’s just a tool. Every awesome tool has a disgusting rule.

2 Likes

Autoboxing was one of the worst design decisions.
Just an example:

list=new List<Integer>()
int e=list.getElementAt(i);

can throw a NullPointerException while auto-converting the element to the int value.

So I am happy that Kotlin does not auto convert types.

Nein. That’s not what I mean. In your example, List is a collection of objects (type Any or Any?) and of course, it cannot be converted to List or List or List (it wouldn’t be a compiled language if that was the case). But if you have a List for example, why can’t I use Double for e while we know that all floats are doubles and there should be no exceptions?

Bonus: Try to use Kotlin for Data Science for example and you will find this even more annoying.

2 Likes

Unfortunately your message is not quite understandable, I think because the angle brackets were stripped by the forum tool. So “it cannot be converted to List or List or List” doesn’t make sense. (Need wrap any text containing angle brackets List<Foo> with backticks, ala Markdown.)

Are you suggesting that Kotlin should allow something like this?

val floats : List<Float> = listOf(1.2f, 2.3f)
val doubles : List<Double> = floats  // compilation error

Wait, was this ever changed?

val a = 2.0
val b = 3
val c = a / b

This currently compiles fine, with no warning or error, and c is a Double.