Scala x Kotlin

Singapore Scala Meetup - 25 April 2018

(for Scala programmers)

About Me

  • Software Engineer at Standard Chartered Bank

 

  • Currently working in:
    • Scala
    • Kotlin

 

  • Currently 'spiking' in:
    • Elixir
    • Elm
  • Giving back to the community:
    • OSS project maintainer
    • Singapore Scala Meetup group organiser
    • Engineers.SG volunteer

 

               _hhandoko

               hhandoko

               hhandoko.com

Context and Goals

Context

  • Business directive for global market rollout:
    • Revised architecture
    • Region-specific requirements
    • New functional and non-functional requirements
  • Challenges:
    • Java 7 code and Java 7 runtime
    • Tight coupling, side effects

 

  • New algorithm was being rewritten in Scala

Goals

  • Improve maintainability:
    • Remove boilerplate code
    • Focus on business logic and flow of data

 

  • Improve testability:
    • Make it easier to write tests for new functionalities
    • Improve test coverage

 

  • Universal distribution image:
    • Support existing environment in WebSphere AS on Java 7
    • Support new environment in OpenShift (Docker + Kubernetes-based) on Java 8

Basically...

Why Kotlin?

  • Personal (subjective) reasons:
    • Used it before

 

  • Technical reasons:
    • Java 7 support
    • Modern language features
    • Good collection libraries
    • Good two-way interop with Java code
  • Other reasons:
    • Endorsement by Google

Equivalent Features

Variables

// Immutable (with type annotations)
val greeting: String = "Hello"

// Mutable (with type inference)
var world = "World!"
world = "Singapore!"

Case / Data Class

// Scala
final case class User(
  name: String,
  age: Int
)
// Kotlin
data class User(
    val name: String,
    val age: Int
)

String Interpolation

// Scala
val name = "Herdy"
val greeting = s"Hello $name!"

val multiline =
  """An ocean voyage.
    |As waves break over the bow,
    |the sea welcomes $name.
  """.trimMargin
// Kotlin
val name = "Herdy"
val greeting = "Hello $name!"

val multiline =
  """An ocean voyage.
    |As waves break over the bow,
    |the sea welcomes $name.
  """.stripMargin

Lazy Delegates

// Scala
lazy val pi: Double =
  22 / 7
// Kotlin
val pi: Double by lazy {
  22 / 7
}

Companion Objects

// Scala
class User {
  def run: Unit = ???
}

object User {
  final val STAMINA = 100
}
// Kotlin
class User() {
    fun run: Unit = TODO()

    companion object {
        const val STAMINA = 100
    }
}

Deconstruction

// Scala
val (first, last) =
  ("Herdy", "Handoko")
// Kotlin
val (first, last) =
    Pair("Herdy", "Handoko")

Type Aliases

// Scala
type Amount = BigDecimal
// Kotlin
typealias Amount = BigDecimal

Similar Features

Nullable Values

// Scala
val dateOpt: Option[LocalDate] = None

val day = dateOpt.map { date =>
  date.getDayOfMonth
}

val today = LocalDate.now
val todayOpt = today // ERROR!
// Kotlin
val dateOpt: LocalDate? = null

val day = dateOpt?.let { date ->
    date.dayOfMonth
}

val today = LocalDate.now
val todayOpt = today // OK!

Extension Methods

// Scala
implicit class RSHelper(rs: ResultSet) {
  def parseString(f: String): String =
    rs.getString(f)
}
// Kotlin
fun ResultSet.parseString(f: String) =
  this.getString(f)

Expression Block

// Scala
val someFunction = {
  ???
}
// Kotlin
val someFunction = let {
    TODO()
}

Algebraic Data Types (ADTs)

// Scala
sealed trait Car

case class BMW(series: String)
  extends Car

case object MercedesBenz
  extends Car
// Kotlin
sealed class Car

data class BMW(val series: String)
    : Car()

object MercedesBenz
    : Car()

Collection Libraries

// Scala
phones.map { phone => phone.model }

phones.map(_.model)

phones.filter(_.model == "Nokia")

phones.head

books.map { case (_, title) =>
  title.toUpperCase
}
// Kotlin
phones.map { phone -> phone.model }

phones.map { it.model }

phones.filter { it.model == "Nokia" }

phones.first()

books.map { (_, title) ->
    title.toUpperCase
}

Pattern Matching

// Scala
car match {
  case BMW(s) => println(s"BMW Series $s")
  case MercedesBenz => println("Mercedes-Benz")
}
// Kotlin
when (car) {
  is BMW -> println("BMW Series ${car.series}")
  is MercedesBenz -> println("Mercedes-Benz")
}

Kotlin from Java Interop

Getter and Setters Signatures

  • Fields automatically generate its getters and setters, unless @JvmField is used

 

  • kotlin-noarg plugin is required to keep existing Java idioms (no-argument constructor)
// Kotlin - with `noarg` plugin
data class User(
    var name: String
    var country: String
)


// Java
User user = new User();
user.setName("Herdy");
user.setCountry("Singapore");

Overloaded Constructors

  • Data class needs @JvmOverloads annotation to create constructor overloads to keep Java signatures

 

  • Functions with default argument values can generate overloads automatically
// Kotlin
data class User @JvmOverloads(
    val name: String,
    val age: Int = 21
)

Static Methods and Fields

  • @JvmStatic annotation is required for static fields within companion objects
// Kotlin
class User {
 
    companion object {
        @JvmStatic
        val stamina =
            BigDecimal(100)

        @JvmStatic
        fun run(): Unit =
            TODO()
    } 
}

Java from Kotlin Interop

apply { }, run { }, also { }

  • apply { } applies mutation to the called object and return its value (i.e. this)

 

  • run { } invokes the expression and returns the result

 

  • also { } invokes the expression and returns the called object
// Kotlin
val user = User()
    .apply {
        this.name = "Herdy"
        this.age = 21
    }.run {
        save(this)
    }.also {
        logger.debug("Persisted!")
    }

Elvis Operator

  • Similar to .getOrElse(...), but for nullable values
// Kotlin
val name: String? =
    UserDAO.get(id)
        ?.let { it.name }
        ?: throw IllegalArgumentException()

Retrospective

Tooling Helps

  • Gradle:
    • Concise syntax, scriptable
    • Supports Scala, Kotlin, and Java in one project
    • Parallel execution

 

  • Lombok:
    • @Data
    • @Slf4j
  • ScalaTest:
    • JUnit test runner
    • Spring base spec

 

  • Gatling

Use Cases

  • Kotlin works great with Java code:
    • Using Java-based libraries / frameworks, e.g. Spring
    • Creating APIs that Java code will consume

 

  • The new Java++™
  • A lot of overlap with what Scala offers...

 

  • But still leverages Java's type system:
    • Type erasure
    • Casting

Lessons Learnt

  • Use Kotlin in core / common module:
    • Interfaces
    • Domain objects

 

  • Break down into modules:
    • Hard boundaries
    • Leverage module dependency graph to parallelise build / task execution
  • Interop can be frustrating:
    • Inaccessible references
    • Java & Scala type conversions
    • Code style and conventions

 

  • Plenty of orchestration for all team members:
    • Agree on approach, long term roadmap

What's Next?

Functional, Async, Measure!

  • Spike pros and cons on using Kotlin Arrow's Option<T>, Try<T>, etc. to keep codebase on same idiom

 

  • Bridging the async gap between Kotlin + Java and Scala
  • Measure the cost of conversions of shims, wrappers, adapters:
    • Micro: ScalaMeter
    • E2E: Gatling

Thanks!

Made with Slides.com