Saturday, 11 February 2017

Kotlin Literals 2

So in the previous post we have seen that Kotlin only supports positive integer literals, which means that half the numbers it supports can't be written as a literal in code. Let's look at other ways to write number literals, maybe as hexadecimal. This is for example used often for Android colors:
val green: Int = 0xff00ff00
This code looks good, right? Android colors use a 4 byte ARGB format, and the above number matches that and would work fine in Java. Well, again, it doesn't work in Kotlin. The compiler gives again a unhelpful message:
The integer literal does not conform to the expected type
What does that mean? It turns out that Kotlin again only supports positive number literals. 0xff00ff00 would be negative when written as a signed integer and so Kotlin thinks this should be a Long. The unnecessarily complicated and ugly solution:
val green = 0xff00ff00.toInt() // Explicit cast to Int
Do you want to do that every time you write down a color ? I don't.

And again, the problem is worse for Long values. Try to write 0xff00ff00ff00ff00 as literal. Doesn't work. As there is no larger type that could be downcast as above, you are really out of options here. Jetbrains recommends the following workaround:
val foo: Long = java.lang.Long.parseUnsignedLong("ff00ff00ff00ff00", 16)
which requires Java 8 and is so ugly, I don't even know what to say.

The good thing is, that Jetbrains is aware of the problems and seems to want to fix this by introducing unsigned types and other improvements.

In the future, I might add a post about String and floating point literals.

Kotlin Literals

Kotlin is a nice language and has many improvements over Java. But not everything is better. I'm not really happy with the literals for example. Let's looks at the following example code:

println(-3.plus(4))

What does this code print? Please make a guess.

...

Should be easy enough, right? -3 plus 4 is 1 obviously. Well, if you try it, you'll see that it prints -7. Why? Because Kotlin interprets this as -(3.plus(4)). Which means that there are no negative literals in Kotlin, only positive ones and the minus is actually the unary negation operator.

This also causes other problems. Let's try to write the minimum Long value as a literal.
println(-9223372036854775808)
The corresponding line in Java works just fine, so Kotlin shouldn't have a problem, right? Wrong! This code doesn't compile. The compiler gives the unhelpful error message "The value is out of range" although the value fits into a Long. Problem is again, that the positive long value 9223372036854775808 really is out of range and Kotlin doesn't understand this as a negative literal but as a positive value with a negation operator. What's the workaround?
println(-9223372036854775807 - 1) // Ugly, but works
Interestingly, the Integer version of this problem works fine.
val foo: Int = -2147483648
Following the Kotlin compiler logic, this line should fail because 2147483648 can only be a Long value and a negative Long value should also be a Long value. But apparently there is some compiler hack in place that prevents this problem for Ints. Why they included the hack for Ints but not for Longs? No idea.

More Kotlin literal shortcomings are listed in the next post.

Monday, 6 February 2017

Do not use Scanner

The Scanner class is often used in programming tutorials and beginner code. It's often used to read user input from System.in or from a text file. But it is a bad class and should be avoided. Here I try to explain why.

The main problem is that it violates the single responsibility principle. The scanner class is doing the following things:
  • reading data from a file or an InputStream
  • interpreting this data using a charset
  • detecting line endings
  • using complex regular expressions to (optionally) parse the data into Integers, Doubles, BigIntegers and so on.
Ideally one class should do one thing and do it well. Splitting a problem into easy manageable subproblems is a key aspect of programming. The Scanner class fails this by trying to do all these things at once.

Because of that, beginners (and experts) are often confused by the Scanner class and rightfully so. For example nextInt() only consumes line endings if it needs to, but nextLine() does always, so if you try to read mixed data, for example an Integer and then a String from the console, you will easily run into problems. For example reading a line after reading an Integer from the console will not read the next line but just the not consumed line ending of the previous line. This is explained here in detail.

Additionally, the Scanner class has really weird exception handling in that it silently eats IOExceptions when reading values and only optionally returns the exception with the ioException() method, which makes it easy to miss exceptions and read incomplete input accidentally.

What should you do instead? As usual, divide a problem into easily manageable subproblems.

Use a BufferedReader to read lines from an input source. The input source could be for example a InputStreamReader reading data from an InputStream in a specific encoding. If reading from the console, remember that System.in is also an InputStream. Or simply use Files.readAllLines(...) when reading from a file.

Then if you have the line, split it into multiple elements if you need. You could use String.split(), but the Splitter class from Google Guava works better for many reasons. If you only need a single value per line, you need neither.

And then use for example Integer.parseInt() to convert the String into whatever data type you need. Or if you need a String, simply use it.

In the end, this sounds more complicated than using a Scanner and you may need a little bit more code, but as a programmer you need to know anyway how to read a file or how to split data or how to convert a String into numbers, so that should be easy. You do not need to know how to use a Scanner (except to understand stupid programming tutorials). And by doing the splitting and parsing yourself, you have much more control about the process and the handling of possible errors.