article banner

Kotlin revolutionary announcement

KotlinConf opening Keynote was full of announcement, that were important for Kotlin developers, but minor from a perspective of multilingual developer. Collection literals are a standard in many languages. Name-based destructuring is widely used in the world of web development.

However, there was an announcement, that might introduce new quality to the world of programming in general. A change, that might be the next big step towards extracting the benefits of both dynamically and statically typed languages.

Let me start with a bit of context. One of the greatest advantages of dynamically-typed languages, like Python, JavaScript or Ruby, it the capability to reference properties that are not defined in code. For instance, in Python, when we operate on tables or CSV files using Pandas, we can reference columns using properties with their names.

import pandas as pd 
df = pd.read_csv('example.csv')
revenue = df.groupby('customer_id').price.sum()

This gives us convenience, but I must say, those properties are not typed, so they will not be suggested by IDE, and if you make a typo, your code will fail at runtime.

In JavaScript, we can reference fetched object properties even though they are not defined anywhere. The tradeoff is the same - if we reference a property that do not exist, the result is undefined, what can easily lead to a runtime exception.

fetch(apiUrl, {
  headers: {
    'Authorization': `Bearer ${bearerToken}`
  }
})
	.then(response => response.json())
	.then(data => {
	  const tweet = data.data;
	  authorImg.src = data.includes.users[0].profile_image_url;
	  authorName.textContent = data.includes.users[0].name;
	  tweetText.textContent = tweet.text;
	  tweetDate.textContent = new Date(tweet.created_at).toLocaleString();
	  retweetCount.textContent = `Retweets: ${tweet.public_metrics.retweet_count}`;
	  likeCount.textContent = `Likes: ${tweet.public_metrics.like_count}`;
	})

Ruby is also utilizing its dynamic character. This is how we could define and use a DB table in Ruby on Rails.

class Post < ApplicationRecord
  belongs_to :user
  has_many :comments
  
  validates :title, presence: true
  validates :content, presence: true
end

post = Post.find(1)
post.title = 'Updated title'
post.content = 'Updated content'
post.published = false
post.save

We cannot do that in statically typed languages. They require type definitions, so to transform JSON to Kotlin object with properties appropriate to JSON property names, we need to define types. To operate on CSV rows using column names, we need a class that would define property names. To operate on database tables, we need classes that specify column types.

@Serializable class User( val id: Int, val name: String, val email: String, ) @Entity(tableName = "users") data class User( @PrimaryKey(autoGenerate = true) val id: Int, @ColumnInfo(name = "first_name") val firstName: String, @ColumnInfo(name = "last_name") val lastName: String, val email: String )

However, this might be about to change! Roman Elizarov showed on KotlinConf 2023 opening Keynote, that new Kotlin Compiler Plugin capabilities will allow type generation during development time. Consider the example below. Inside filter, we can reference tags because this is the name of one of columns in KotlinSO.csv. What is more, inside groupBy we can reference year property, because such colums was added a line before.

This is revolutionary to me - we can have the best of dynamically-typed languages, while keeping code perfectly static-typed.

I assume this approach can be pushed forward, instead of defining complex DTO objects for database, they could be generated by some plagin based on actual database columns structure. This can already be done using SQLDelight.

val database = Database(driver) val playerQueries: PlayerQueries = database.playerQueries println(playerQueries.selectAll().executeAsList()) // Prints [HockeyPlayer(15, "Ryan Getzlaf")] playerQueries.insert(player_number = 10, full_name = "Corey Perry") println(playerQueries.selectAll().executeAsList()) // Prints [HockeyPlayer(15, "Ryan Getzlaf"), HockeyPlayer(10, "Corey Perry")] val player = HockeyPlayer(10, "Ronald McDonald") playerQueries.insertFullPlayerObject(player)

Another example of how such capabilities were used is references to view elements using their names in Kotlin Android Extensions.

import androidx.appcompat.app.AppCompatActivity import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity(R.layout.activity_main) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) textView.text = "Hello, Kotlin Android Extensions!" button.setOnClickListener { doSomething() } } }

The beauty of that, is that those properties are statically typed and suggested by IDE. So we have maximum of sefaty and convenience.

Annotation processing

To some degree, it could be considered an evolution of annotation processing. It can be used to generate classes based during compilation time. Its biggest limitation, is that we need to build project to make it work. Compiler Plugins can influence our types and existing code during development time, this is a huge advantage, that opens a whole range of new possibilities.

What is the future?

Java Annotation Processing was a revolution that changed programming. I feel that Compiler Plugin capabilities will become another revolution, that will go outside of Kotlin world, influancing other languages.