Thoughts for 2019
It's always interesting to look at the year ahead in technology, and so, some thoughts about 2019! These are less predictions, more extrapolations of what's already happening.
Read MoreIt's always interesting to look at the year ahead in technology, and so, some thoughts about 2019! These are less predictions, more extrapolations of what's already happening.
Read MoreHerein some thoughts about software technology 2018. I won't call these predictions, more extrapolations of what's already happening and what the second order effects might be.
Read MoreThis is one of a series of posts on languages, you can read more about that here.
Herein a grab bag of observations on the programming language Kotlin, positive and negative. First, a large caveat - I have no production experience with it, nor is it actually a released 1.0. It’s still in early development and just a couple of years old at this point.
First off, it’s close to Java in terms of default syntax relative to Groovy, Clojure or Scala. This should make it easier to pick up for Java programmers and seems to be an intentional part of the design. As with Scala and again Go, declaration order of types and names is reversed relative to C++ and Java, which improves comprehension ('a of Int' is easier than 'an Int named a'):
fun sum(a: Int, b: Int): Int {
return a + b;
}
Semi-colons are optional, braces to an extent, and return types can be inferred
fun sum(a: Int, b: Int) = a + b
One thing worth noting early is we didn’t have to declare a class. Like Scala, Kotlin supports functions directly (the `fun` keyword). Also like Scala, we can have immutable and mutable variables via the val and var keywords:
val a: Int = 10 // read only
val b = 10
val c: Int
c = 10
var d = 10 // read write
d += 1
JetBrains have said they were influenced by Scala when designing Kotlin and so far it shows in terms of basic structure.
Conditionals are what you’d expect, but note that `if` is an expression not a keyword:
fun max(a : Int, b : Int) : Int {
if (a > b)
return a
else
return b
}
// because if is an expresssion; you can add `else if` as well
fun max(a : Int, b : Int) = if (a > b) a else b
Iteration allows the use of the in expression as well as support for ranges, akin to Python for both looping and membership checking:
for (arg in args)
print(arg)
for (a in 1..10)
print(a)
if (arg in 1..10)
print("arg")
if (o in c) // c.contains(o)
print("o")
Like the case is, there is also a corresponding `!in`
if (o !in c)
print("no")
While and Do/While work like Java. The when
expression is like a switch statement on steroids, containing a set of branch expressions to match against. If Kotlin can’t confirm your coverage is exhaustive, you’ll add an else block as the default handler. In the example below, the return type is Unit but since when is also an expression if can return a value. Note than you can have multiple values match on the same branch, and the evaluation can be an arbitrary function or expression:
when (a) {
0, 1 -> print("a is zero or one")
lt(a, 0) -> print("a is negative")
in 2..10 -> print("a is in the range 2 to 10")
else -> {
print("a is $a")
}
}
This is pretty impressive and directionally goes towards Scala’s (really, quite powerful) match expression. I find it a strong point of differentiation relative to Java.
Kotlin can autocast. Instead of explicit casting you can use is
and to `!is` as handlers:
fun len(o : Any) : Int? { // note the nullable return type
if (o !is String)
return null
return o.length // autocast
}
Kotlin has the usual set of primitive types. There’s no support for automatic type widening (eg byte up to int). Instead you have to explicitly call a conversion":
val b : Byte = 1
val i : Int = b // won't compile
val j : Int = b.toInt() // ok
There are a set of builtins for numeric and bitwise operations (eg `shl` for shift left, xor
for exclusive or, and so on).
One thing worth mentioning is chars aren’t numbers, and you have to explicitly treat them as such:
val c = '0'
if (c == 0) { // won't compile
....
}
Strings like Java are immutable but Kotlin supports 'raw' multiline strings akin to Python:
val l = """
Then they sent me away to teach me how to be sensible
Logical, responsible, practical
"""
There also is support for string interpolation:
val i = 10
val s = "10"
val a = "i = $i, $s.length is ${s.length}"
print(a) // 'i = 10, s.length is 2'
And arguably from this point Kotlin veers from Scala never mind Java. Kotlin has built in null safety—by default—if you want something to be nullable you have to say so using the `?` operator, and like Groovy you have options to build up safe call chains with it, as well as escape valves with `?:` (provide a fallback), and `!!` (force an NPE):
var a: String = "asd" // can't be null assigned
...
val c = a.length() // guaranteed to not NPE
var b : String? = "asd"
b = null // acceptable in the 80s
b?.length() // returns null, doesn't NPE
foo?.bar?.baz?.length // returns null if any of are null
val len = b?.length() ?: 0 // if b is null send back 0
val len = b!!.length() // force an NPE if null
The not null as default for me is a key language feature that enables software as engineering. Yes you can work around it and program to the level of actual discipline, but to do so you have to write that code and check it in. Every NPE is assignable to an author and not easily passed along as surprising behavior.
Classes have a single constructor defined as part of the class statement rather than a separate constructor and an anonymous block defines the code to invoke on object construction and class fields can be assigned as vals or vars.
class Point(x: Float, y: Float) {
val x = x
val y = y
{
print("created a point")
}
}
and you can elide the internal field assignments using val/var:
class Point(val x: Float, val y: Float) {
{
print("created a point")
}
}
Classes are final by default in Kotlin, the opposite of Java. The open keyword is used to define an extensible class:
open class Pizza(base: Base, toppings: Toppings, sauce: Sauce)
class DeepDish(base: Base, toppings: Toppings, sauce: Sauce): Pizza(base, toppings, sauce)
Overriding must be explicitly declared using override, again the opposite of Java.
For construction, Kotlin doesn’t have new—objects are created as expressions:
val pt = Point(0.1, -1.5)
However class functions are open by default not closed per class inheritance: you have to add final to stop a function override. I get it, but it’s annoyingly inconsistent.
Kotlin introduces the idea of a Trait. They correspond to interfaces with optional methods, but don’t enable state the way abstract classes do: you can declare a field but not assign it. You can define a trait in. way that lets you access state, but not hold it:
open class PointCoords(val x: Float, val y: Float)
trait PointOps : PointCoords {
fun scale(s: Int): PointCoords {
PointCoords(s.toFloat() * x, s.toFloat() * y)
}
}
// don't define your cartesian stuff like this at home folks
class Point(x:Float, y:float): PointCoords(x, y), PointOps {}
We can prefix a class with data to declare it’s basically just a record/struct:
data class Point(val x: Float, val y: Float)
that gives us equals/hashCode/toString and more interestingly a copy function that lets us create a new object from an existing one, but passing just the values we want changed, as well as destructuring:
// User is a data class
val jacksShadow = User(name="Jack", dob=1986)
val janglingJack = jacksShadow.copy(dob=1994)
// destructure
val (name, dob) = janglingJack
I believe there are some good design decisions for the most part. Inheritance is less commonly needed that was thought in the industry at the time Java was created nearly twenty years ago, running down override issues is easier via looking at the code versus running or debugging it and problems with hackaround approaches like Lombok. Traits allow functionality to be stacked in a principled manner while dodging the coupling of abstract classes and the limitations of interfaces. Copy construction is tedious and error prone in Java. Overall similar in philosophy but different in doctrine to Scala and both move us away from the late 20th century paradigm of PIE (Polymorphism, Inheritance, Encapsulation) object orientation to something far better, typed functional programming. I’ll have more to say about object construction, lack of static and close d by default later.
Kotlin allows us to add functionality without subclassing via extension functions. This is much the same as C# and an interesting choice. You declare a function prefixed with a receiving class
fun Point.inverse(x : Float, y : Float) {
// ...
}
This also works for more precise types like generic collections, so `fun MyMutableList<Int>.swap(x : Int, y : Int) {...}` is perfectly fine. Extension functions are resolved statically and if the target has the same named function, the target will win and the extension will not. If you want to extend something in a different package space, you have to import that space. This is all easy to test for so as to avoid footguns and post-hoc damage. YMMV may vary but this is way, way better for me than global monkey patching in languages like Ruby or weird dynamic hacks to modify methods locked away in Java libs. It looks like a well thought out response to the open-closed principle and the reality of real world programming needs.
Kotlin supports functions as return values and functions as arguments. It also has a set of extension methods for collections like map and filter.
// accept bar, a noargs function that returns a generic T
fun foo<T>(bar: () -> T): T {
// ... we can call bar() from here
}
The bar above stands from a function type, a signature for a function (or what some Scala folks call a function shape). In this case bar’s type is ‘() -> T
’ , that is, a function that accepts no arguments and returns whatever the T generic resolves to.
These represent big steps up from traditional Java, although Java 8’s recent introduction of SAM, lambdas and streams all indicate similar language design trends are finally being absorbed, if not to the extent Kotlin proposes for an eventual 1.0 release.
On to some things that bother me about Kotlin.
Ownership. It’s another single vendor language. As much as I understand why JetBrains created it, arguably dealing with single company owned languages is why it and other candidate ‘better Java’ languages were even created. Ultimately it needs to end up in an independent body or foundation. Licensing alone is not the right bar for something as fundamental as a language.
Bits and pieces. You can work around null handling, one of the standout and most courageous design decisions the language makes. As much as I appreciate the not null as default I’m left wondering could the language go one step further. I feel like an acid test for production code will be the number of ? and !! qualifiers that pop up. Defaults matter, but I’m a bit less excited about a language that is willing to incur 100M dollar mistakes while opining about 1B dollar mistakes, while admitting this is pretty harsh criticism. Namespaces I think might end being confusing from the call site, but ok, functions can be qualified or even boxed into classes at the cost of starting to look like those JavaUtils.foo() support classes. I’m interested to see how real world code will handle statics coming from Java. Primitive boxing might be a problem wrt efficiency, but can be engineered out over time. I don’t love the way there’s multiple approaches to object construction, it feels unnecessary and breaks a bit with the overall streamlined design of the language. Closed by default I get in the sense of design ethos, but is surely going to be painful in practice given the level of need to workaround problems in 3rd party libraries and interop demands against a metric tonne of existing Java code. Making everything final isn’t a great time in Java, and of all the advice one could take from Effective Java, this wouldn’t be too high up for me. Data classes, a brilliant idea, look directly impaired by this choice by being not open.
Differentiation. Kotlin looks to be positioning itself as a better Java contender throwing its hat in with Clojure, Groovy and Scala, each of those having a different notion of what better might be. Even more so—JetBrains I sense want Kotlin to become the go to application language on the JVM. Of the crop, Kotlin aligns mostly to Scala in sensibility. At the same it takes a very different position on the power/expression/learning axes and I feel does a better job of meeting Java programmers where they'‘re at than any option to date, including Groovy. Nominally that should be a good thing. But between the time Kotlin was conceived and then announced, and before the time it ships a 1.0 GA, Java 8 landed. 8 is likely the most significant Java release ever, and I include Java 1.0 here. We mentioned, streams and lambdas earlier, but under the hood, invokeDynamic could allow Java itself to evolve in interesting ways and not just be an option for other languages. The question on the long haul becomes to what extent will Kotlin usefully differentiate, and, stay compatible with Java language and underlying JVM improvements? (SAM already seems like a headache to contend with for a Kotlin 1.0). If Java’s evolution continues to be slow, then yes, Kotlin maintains a clear advantage combined with familiarity. Timing might in hindsight also be a factor, in the sense Kotlin may have arrived a few years too late to really take the lion’s share of existing programmers away from Java.
But overall, Kotlin looks impressive, and my criticism of a single vendor notwithstanding, a language designed by toolmakers and to be strongly interoperable with Java is something to be excited about.