Advent of Kotlin

Saturday, December 26th, 2020 07:14 pm
flwyd: (java logo)
Each December for the past several years, Advent of Code has presented a series of 25 daily programming challenge, with each problem adding to a Christmas-themed narrative. I think I'd seen references to AoC in the past but hadn't paid it any mind. This year, my team at work is evaluating Kotlin for adoption in our Android Java codebase, so a small daily excuse to get experience with the language seemed promising. Plus, there's a global pandemic so it's not like I've got any holiday parties to attend.

The event was more fun than I'd anticipated. Challenges are released at midnight America/New_York each night, and there's a time-to-completion leaderboard, so there's a competitive challenge aspect to get the juices flowing. This wasn't great for health, though—on a couple nights I started programming at 10pm America/Denver while already tired and didn't go to bed until 3am, whether because I was too sleep deprived to effectively debug or because I was having fun giving hints on the contest's subreddit. Mostly it was fun because the problems are small enough to do in one sitting and often involve an interesting algorithm. Lots of participants give themselves an additional challenge, like using a different programming language each day or using an unusual or challenging language—I saw someone posting solutions in the m4 macro language and some folks using Excel. Lots of folks create visualizations of their algorithm solving the problem; this year's challenges involved several which were based on Conway's Game of Life which naturally offer interesting visualizations.

My experience with Kotlin was a bit mixed. Kotlin is a programming language designed to run on the Java Virtual Machine and play well with Java code, but with a more expressive syntax and some features informed by two decades of programming language evolution since Java came into the world. It is perhaps most widely used in the Android ecosystem where some of its features help cover for poor Android framework design and API choices and where its coroutine concurrency model is a better fit for client application programming than Java is. Kotlin can also run in JavaScript and iOS environments, offering a hope of cross-platform shared logic. I've seen enough cross-platform efforts fail to be widely adopted to be skeptical on this front, though.

Using Kotlin for Advent of Code offered several benefits over Java. First, the heavy type inference and lower repetition and boilerplate reduced the number of symbols that had to be typed, which is nice for short programs, particularly one with Fake Internet Points for programming quickly. The standard library provides a lot of handy utilities like ranges, a typed Pair class and check/require (functions which concisely throw an exception if the program is in an invalid state) for which Java needs a library like Guava. when blocks were also handy in many AoC puzzles, and a lot friendlier than a chain of if/else conditions. Kotlin's fluent collection transformations (filter, map, sum, and friends) feel a little more expressive than Java Streams, and I found multiple occasions where "potentially infinite sequences" were helpful. Coroutines (which power sequences) are, I think, Kotlin's biggest selling point, and while most Advent of Code problems don't particularly benefit from concurrency, I found yielding values from a recursive function easier to implement and reason about than accumulating a list that gets returned up the chain.

I'm not entirely won over on Kotlin, though. My first gripe is that the language is at risk of falling into the C++ and Perl trap wherein the language provides multiple ways to do very similar things and two pieces of code which do the same thing look very different. This in turn can create a cognitive impediment when reading code written by a different programmer or team. One example of this is the distinction between properties and no-arg methods. In Kotlin, one writes list.size as a property but list.isEmpty() as a method and I've been unable to find guidance on when to use one rather than the other for read-only state.

Second, one of Kotlin's selling points is nicer handling of nulls, since nullability is part of a type definition (String? is nullable, String is not). This is handy, and reduces boilerplate, particularly with null-happy APIs like Android. But it also means the compiler forces you to handle null cases which you know semantically can't occur, such as calling .max() on a collection that you know is not empty. This leads to a proliferation of method name pairs, one of which throws an exception and one of which returns null (elementAt/elementAtOrNull/elementAtOrDefault, getValue/get/getOrDefault, maxBy/maxByOrNull, maxWith/maxWithOrNull…). This also isn't entirely consistent within the standard library: list[5] throws an exception if the list has fewer than six elements, but map[5] returns null if that key is not present. The need for "OrDefault" method variants also seems a bit odd when the language also provides the Elvis operator (?:) for null-coalescing.

Third, the impression that Kotlin is basically Java with nicer syntax can lead to unpleasant surprises when the Kotlin standard library has a slightly different implementation to a similar method in Java. For example, in Java, String.split with an empty argument returns an array with one character per string: "cake".split("") is the same as new String[] {"c", "a", "k", "e"}. The same behavior holds true in JavaScript, Python, Perl, and perhaps dates back to AWK. Kotlin, on the other hand, returns an array with empty strings at the beginning and end: "cake".split("") is the same as arrayOf("", "c", "a", "k", "e", ""). What's worse, the behavior of splitting on an empty string or pattern is not documented in Kotlin, so I don't know if it's a bug or an intentional choice.

This brings up another of my Kotlin complaints: documentation. There are plenty of valid complaints about Java's verbosity, but the clarity and completeness of Javadoc in the Java world is wonderful. I very rarely have to read the code in the JDK or a widely-used library to understand how it will handle a particular input. (The same cannot be said for Ruby, for example.) Kotlin seems to prefer more terse documentation and rarely gives sample code, so you're often left to figure it out yourself, experimentally. The Kotlin web interface for API documentation also has some notable room for improvement, like proper handling of "Open in new tab" clicks.

My final Kotlin complaint that cropped up during Advent of Code is a sneaky one. One of Kotlin's neat features is extension methods: you can define a method on a type defined by someone else, like operator fun Pair<Int, Int>.plus(other: Pair<Int, Int>) = Pair(first + other.first, second + other.second). This can help the readability of code with several steps by chaining all method calls from top to bottom, whereas Java would end up with a mix of static method calls wrapped around method chains. This feature, however, comes with a major downside: extension methods are resolved statically against the declared type of the receiver. They are not dispatched dynamically, despite having identical syntax as dynamically dispatched methods. A concrete example I ran into: a function which checks the neighboring cells of a 2-D grid used the following code:
fun checkNeighbors(x: Int, y: Int) {
  for (i in (x-1..x+1).intersect(0 until height)) {
    for (j in (y-1..y+1).intersect(0 until width)) {
      // do something with grid[i][j]
    }
  }
}

This expresses "go through all the cells from above left to below right while staying inside the grid bounds" by using the intersection of pairs of ranges. Unfortunately, this is an O(n^2) algorithm because intersect is defined as an extension method of Iterable, so it runs through all width columns for each height row, even though at most three of each are relevant. I could write a specialized IntRange.intersect(other: IntRange) = IntRange(max(start, other.start), min(endInclusive, other.endInclusive)) extension method, and it would improve the complexity in this code to O(1). But if someone passed an IntRange to a method declared to take an Iterable or a ClosedRange, an intersect call on that argument, the inefficient generic version would be used. This contrasts with Java 8's similar mechanism, default methods on an interface, which allow implementations to provide a specialized version dispatched at runtime.

Returning circularly to the "too many ways to do the same thing" problem, here are some efficient ways to write that grid code in Kotlin:
for (i in (x-1).coerceAtLeast(0)..(x+1).coerceAtMost(height-1)) {
  for (j in (y-1).coerceAtLeast(0)..(y+1).coerceAtMost(height-1)) {

for (i in (0 until height).let { (x-1).coerceIn(it)..(x+1).coerceIn(it) }) {
  for (j in (0 until width).let { (y-1).coerceIn(it)..(y+1).coerceIn(it) }) {

for (i in x-1..x+1) {
  if (i in 0 until height) {
    for (j in y-1..y+1) {
      if (j in 0 until width) {

for (i in (x-1..x+1).filter((0 until height).contains)) {
  for (j in (y-1..y+1).filter((0 until width).contains)) {

but I'm really not sure which is the most idiomatic.

Ruby Soup

Thursday, November 20th, 2008 12:06 am
flwyd: (java logo)
I had a thought at work the other day. Java has a strict one-parent-class rule. Philosophically, this is a good match in domains like taxonomic biology (a phylum is in one kingdom, a genus is in one family). But it's a terrible match in domains like cooking. A soup inherits flavors from many sources. You can add meat to what was originally a vegetable stew. Ruby is a much better language for programming a curry.
flwyd: (java logo)
The type java.lang.Object cannot be resolved. It is indirectly referenced from required .class files.

Chaos Game Applet

Sunday, April 22nd, 2007 01:52 pm
flwyd: (java logo)
[Chaos Game] I spent most of yesterday and the first part of today writing a Java applet implementation of the Chaos Game. I find that the color schemes help significantly to understand why configurations lead to the patterns they do. The original description (which explains verbally better than I have) just featured monochrome images.

While I write Java code every day at work, most of it is data-focused. Data-focused GUIs (put this field over here, put these buttons in this panel over here) are important and often tricky to get right, but it's been years since I did anything with public void paint(Graphics g). Playing with code which requires no database, no metadata configuration, and no client-server communication was also refreshing.

You'll need at least Java 1.4 to view the applet. I compiled from Eclipse running under Java 1.5 and while I don't think I used any new API methods I didn't have a 1.4 JVM convenient for testing. Let me know if you can't get it to work.
flwyd: (java logo)
If a poor and despised Indian tried to get a job at a company testing Java software they would throw him out with a ClassCasteException.

throw null

Thursday, January 18th, 2007 03:45 pm
flwyd: (java logo)

I saw some typo Java code in my CVS update today which called throw null. I wondered just what that would do. null can be cast to any object, so I thought it might get caught by the first catch clause. Alternatively, I thought it might escape all catch clauses (dangerous!). The answer?

	try {
	    throw null;
	} catch (IllegalArgumentException e) {
	    System.err.println("Caught IllegalArgumentException " + e);
	    e.printStackTrace();
	} catch (NullPointerException e) {
	    System.err.println("Caught NullPointerException " + e);
	    e.printStackTrace();
	} catch (Throwable t) {
	    System.err.println("Caught Throwable " + t);
	    t.printStackTrace();
	}

Caught NullPointerException java.lang.NullPointerException
java.lang.NullPointerException
	at TestStuff.main(TestStuff.java:156)

Java (even pre-1.5) automatically creates a NullPointerException if you throw null. It's like very specialized autoboxing.

flwyd: (java logo)
Save me Edsgar! I just wished I could use a goto statement in Java. My code's primary purpose is to take a bunch of fixed-width data and save it to another file in a tab-delimited format. I decided to add the ability to save the layout. If the layout file already exists, I prompt the user with a Yes/No/Cancel dialog to overwrite. If they cancel, I don't want to write anything (layout or data) and just return from the function. If they say no, I won't write the layout file, but I still need to write the data. If they say yes, or if the file didn't already exist, I write the layout file and then write the data. With a goto, I could break out of my if conditions and just write the data. But without the goto, my options are to set a status flag, write a function to prompt and write the layout, returning a boolean if it was canceled so I can refrain from writing data (probably the best solution), or the following solution: write a one-iteration for loop and break to a label, like so:
Read more... )
flwyd: (Trevor shadow self portrait)
At work we use iText, a free library for manipulating PDFs. The library's author makes his money by giving away cool software that's complicated enough that developers are motivated to buy the book so they can use it well. We just bought said book, and I want royalties:

Trevor armed and ready to do battle with code

Fun Java Facts

Thursday, April 27th, 2006 06:12 pm
flwyd: (dogcow moof!)
Running JVM 1.4.2_08:
System.out.println("1 + --0 = " + new BigDecimal("1").add(new BigDecimal("--0")));
1 + --0 = 1
System.out.println("Int " + new BigDecimal("--00").intValue());
Int -1
System.out.println("Double " + new BigDecimal("--0").doubleValue());
ArrayIndexOutOfBoundsException: -1
System.out.println("BigDecimal " + new BigDecimal("--0").toString());
ArrayIndexOutOfBoundsException: -1
System.out.println("BigDecimal --42 = " + new BigDecimal("--42").toString());
NumberFormatException: Illegal digit
May 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 2025

Most Popular Tags

Expand Cut Tags

No cut tags

Subscribe

RSS Atom
Page generated Friday, June 6th, 2025 09:06 am
Powered by Dreamwidth Studios