Type-Safe and Clean Coding: The Benefits of Type-Inference

Type-interference is a great programming feature that helps coders write clean, readable code in a reasonable amount of time. Learn more here.

Writing type-safe language while maintaining less boilerplate code is an important aspect of programming languages in terms of developer’s productivity. Because type-safe code is less error-prone and less boilerplate code leads to more readable code, both together means reduced development time. Type-inference is a great programming language feature that maintains this balance.

Developers usually read more often than write. Thus, even if the source code will end up being processed by the computer, most of the time our focus is putting it into more human-readable form. At some point, we pay attention to the UX principles, like:

  • Humans have a limited attention span, so source code should help to spend this attention wisely. Information comes at a cost, so the longer the code is, the more overwhelming it is to read.
  • Sometimes, less means more. Short code may look brilliant, but it amplifies the time that others must spend on it. We should provide just enough information. Implicit values, if we do not abuse them, are the fix for this.

Let’s introduce an example of this. It is well-known that .json is used as an application configuration. Imagine we want to build up a tcp connection. What we need is to read the connection information from the config file and set the connection builder’s fields based on it.

"config": {
 "connection": {
 "name": "Some TCP Connection",
 "address": "127.0.0.1",
 "port": 8000,
 "bufferSize": 16384,
 "enableReconnect": true,
 "reconnect-time": 3,
 "username": "sercan",
 "password": "karaoglu"
 }
}

JSON is human-readable and the type of information is very clear. However, while we are mapping them to an object, we still write things like:

val connectionBuilder = new ConnectionBuilder()
    connectionBuilder.setAddress(json.getJsonObject("config").getJsonObject("connection").getString("address"))
                     .setName(json.getJsonObject("config").getJsonObject("connection").getString("name"))
...etc

From the perspective of the developers, what they only care about is getting the value of the field. Developers usually don’t care first about getJsonObject and then about getString.

Therefore, based on UX principles, this would look better as shown here:

val connectionBuilder = new ConnectionBuilder {
        setName(conf\\"connection"\"name")
        setAddress(conf\\"connection"\"address")
        setPort(conf\\"connection"\"port")
        setBufferSize(conf\\"connection"\"bufferSize")
        setUserName(conf\\"connection"\"username")
        setPassword(conf\\"connection"\"password")
        setEnableReconnect(conf\\"connection"\"enableReconnect")
        setReconnectTime(conf\\"connection"\"reconnect-time")
    }

Here, we reach the fields without using their type information. Instead, we get them like they are some kind of information stored under some path. This could happen through the implicit values and type inference feature of Scala language. Below, you can see the step by step explanation of how it works.

First, we need to define wrapper for our existing JsonObject class so that we can use methods like \\, \.

class JsonWrapper(val conf: JsonObject) {
  def \\(next: String) = new JsonWrapper(conf.getJsonObject(next))

}

object JsonWrapper {
  implicit def wrap(conf: JsonObject) = new JsonWrapper(conf)
}

This will cause JsonObject to be wrapped upon method call \\. Our next objective is to wrap methods like getString, getInteger, getBoolean, getJsonArray, so that we can use the abstraction of \ method to reach the fields. Hence, we should also wrap getters using the GetterWrapper trait and define another method \ in the JsonWrapper class.

import io.vertx.core.json.{JsonArray, JsonObject}

    trait GetterWrapper[+T] {
      def get(conf: JsonObject, next: String): T
    }

    class JsonWrapper(val conf: JsonObject) {

      def \\(next: String) = new JsonWrapper(conf.getJsonObject(next))

      def \[T](next: String)(implicitex: GetterWrapper[T]): T=ex.get(conf,next)

    }

Here, method \ has type parameter T. This parameter’s value will be determined by the type inference. For example, setAddress method of ConnectionBuilder accepts String so T will be String and the available implicit value will be chosen by compiler and placed as variable ex. Therefore, we need to define the implicit values like this…

object JsonWrapper {

  implicit object StringGetter extends GetterWrapper[String]{

    def get(conf: JsonObject, next: String) = conf.getString(next)

  }

  implicit object IntGetter extends GetterWrapper[Int]{

    def get(conf: JsonObject, next: String) = conf.getInteger(next)

  }

  implicit object BooleanGetter extends GetterWrapper[Boolean]{

    def get(conf: JsonObject, next: String) = conf.getBoolean(next)

  }

  implicit object ArrayGetter extends GetterWrapper[JsonArray]{

    def get(conf: JsonObject, next: String) =conf.getJsonArray(next)

  }

  implicit def wrap(conf: JsonObject) = newJsonWrapper(conf)
}

…so that we can use them like this:

import app.connection.ConnectionBuilder

import io.vertx.core.json.{JsonArray, JsonObject}

import scala.language.implicitConversions

import scala.io.Source

import app.util.JsonWrapper._

val conf=new JsonObject(Source.fromFile("git/json-reader/conf/conf.json").mkString) \\ "config"

val connectionBuilder = new ConnectionBuilder {

        setName(conf\\"connection"\"name")

        setAddress(conf\\"connection"\"address")

        setPort(conf\\"connection"\"port")

        setBufferSize(conf\\"connection"\"bufferSize")

        setUserName(conf\\"connection"\"username")

        setPassword(conf\\"connection"\"password")

        setEnableReconnect(conf\\"connection"\"enableReconnect")

        setReconnectTime(conf\\"connection"\"reconnect-time")

}

connectionBuilder.toString

For those who want to see the full source code and give it a quick try, you can clone the project’s repo.

VIAType-Safe and Clean Coding: The Benefits of Type-Inference
SHARE
Sercan Karaoglu had his BS. in Mathematics Engineering Department of Istanbul Technical University. Currently, he develops High Throughput-Low Latency Reactive Microservices and Reactive Stream applications. And passionate about Deep Learning & Machine Learning. He is currently studying his MSc @Department of Computer Engineering and field of Big Data Analytics and Management at Bahcesehir University https://www.kaggle.com/sercankaraoglu https://github.com/SercanKaraoglu http://vertx.io/materials/

LEAVE A REPLY