Slide 1

Slide 1 text

(My journey with) Kotlin Alexis Seigneurin

Slide 2

Slide 2 text

My background • VB → Perl → C → C++ → Java → C# → Java → Scala • Statically typed languages (except Perl) • Almost exclusively using Scala for 3 years

Slide 3

Slide 3 text

Why I looked at Kotlin • Now contributing to a Java project • … and it hurts! → I want to write useful code (not generate code)

Slide 4

Slide 4 text

What hurts (me) in Java

Slide 5

Slide 5 text

POJOs • Getters/setters • Can intercept assignation/retrieval • No one does that in practice → Boilerplate code • Convention for naming getters/setters • Not enforced / may not be respected • Getting/setting values = method calls :-( • Not using ‘=‘ • Step-by-step debugging is unnecessarily complex • Have to write equals() / hashCode() • Not immutable

Slide 6

Slide 6 text

POJOs on my project $ wc -l src/main/java/com/…/**/*.java | tail -n 1 2059 total $ wc -l src/main/java/com/…/dto/*.java | tail -n 1 485 total $ wc -l src/main/java/com/…/model/*.java | tail -n 1 105 total • → 590 / 2059 lines = 29% boilerplate code • Code reviews…

Slide 7

Slide 7 text

No type inference (or so few) NewTopic newTopic = new NewTopic(topicName, topicParameters.getNumPartitions(), topicParameters.getReplicationFactor()); Duplicated type Method calls… just to get values Parsers no longer need semicolons (this is 2017)

Slide 8

Slide 8 text

Streams API is verbose List userAclBindings = aclBindings.stream() .filter(aclBinding -> aclBinding.entry().principal().equals(filterUser)) .collect(Collectors.toList()); Boilerplate Boilerplate

Slide 9

Slide 9 text

No operator overloading List userAclBindings = aclBindings.stream() .filter(aclBinding -> aclBinding.entry().principal().equals(filterUser)) .collect(Collectors.toList()); how about? == for object equality === for reference equality

Slide 10

Slide 10 text

No string interpolation MessageFormat.format("{0}://{1}{2}:{3}", securityProtocol, instanceName, domain, port).toString() new RuntimeException("Tag [" + tagKey + "] not found") (No multiline strings either)

Slide 11

Slide 11 text

One class per file • ClusterDto.java public class ClusterDto { private String clusterId; private NodeDto controller; private List nodes; … • NodeDto.java public class NodeDto { private int id; private String host; private int port; private String rack; …

Slide 12

Slide 12 text

Define a “function” @FunctionalInterface public interface KafkaAdminFunction { T apply(AdminClient client) throws InterruptedException, ExecutionException; } Function = interface with one method!

Slide 13

Slide 13 text

What are our options?

Slide 14

Slide 14 text

Java 9? • Jigsaw, new GC, HTTP 2… • Syntax improvements (lol) • try-with-resource with a pre-defined variable • diamond operator on anonymous inner classes • interface with private methods

Slide 15

Slide 15 text

Java 10? • Value types • Reified generics • …

Slide 16

Slide 16 text

Scala • Extremely powerful • Can be overwhelming • Not acceptable in my team • Scala - Java interop isn’t the best • Requires a stop-the-world project migration

Slide 17

Slide 17 text

Non-JVM language? • Language change + ecosystem change → Too disruptive

Slide 18

Slide 18 text

Other JVM language? • Clojure → Too functional • JRuby, JPython… → Clunky • Groovy → Too dynamic • … • Kotlin → YES!

Slide 19

Slide 19 text

Kotlin

Slide 20

Slide 20 text

Kotlin • JVM language - Initiated by JetBrains • Intent: • make the code concise + readable • make the code safer than pure Java • Made for 100% interoperability with Java • Migrate one class at a time • Statically typed • Compiles faster than Scala (troll)

Slide 21

Slide 21 text

Maturity? • Tooling support • Build tools: Maven, Gradle • IDE: mature support in IntelliJ IDEA (who cares about Eclipse) • Official programming language on Android • Extended support for Kotlin in Spring 5 • (To write more idiomatic code)

Slide 22

Slide 22 text

Interoperability • Migrate one class at a time • IntelliJ can convert a class / a package at once • Perfect interoperability • Call Java from Kotlin (seamless) • Call Kotlin from Java (seamless)

Slide 23

Slide 23 text

Java code Kotlin code

Slide 24

Slide 24 text

Basic syntax • Defining a class + methods class KafkaDescribeClient(val adminClientFactory: KafkaAdminClientFactory) { fun describeCluster(region: String, cluster: String): ClusterDto { … } } Function name: Type Constructor with 1 parameter

Slide 25

Slide 25 text

Basic syntax • Defining an immutable variable val region = "us-east-1" val cluster: String = "engine" val adminClient = KafkaDescribeClient(adminClientFactory) • Defining a variable (you don’t really need that) var something = 12 something = 42 No `new` keyword

Slide 26

Slide 26 text

Basic syntax • No `static` methods • Define a function in a file (in a package, no class) fun nodeToDto(node: Node): NodeDto { return NodeDto(node.id(), node.host(), node.port(), node.rack()) } • Import the functions of the class import com.….dto.* • Call the function nodeToDto(controller)

Slide 27

Slide 27 text

Basic syntax • Reference equality === !== • Structural equality == → equals() Note that the `==` operator in Kotlin code is translated into a call to [equals] when objects on both sides of the operator are not null. • … meaning it’s safe, and `o == null` still works

Slide 28

Slide 28 text

Strings • String comparison: use ‘==’ if(port == "9092") { • String interpolation private fun buildUri(securityProtocol: String, instanceName: String, port: String): String { return "${securityProtocol}://${instanceName}${domain}:${port}" } • Multi-line strings System.err.println(""" ========== Data checking ========== Purpose: checks data quality of messages in a topic. - Kafka brokers = ${config.kafkaBrokers} - Kafka group ID = ${config.applicationId} ... """)

Slide 29

Slide 29 text

Data classes • Data class = (immutable) POJO data class AclParameters( val operation: AclOperation, val user: String, val hostname: String?, val groupId: String?) • Create an instance (no `new`) val aclParams = AclParameters(AclOperation.READ, "u1", "h1", “g1") • equals() / hashCode() / toString() → generated • From Java = POJO with a constructor + getters • For Jackson: there’s a module

Slide 30

Slide 30 text

Data classes • Immutable → copy if you need to update val aclParamsCopy = aclParams.copy(groupId = groupName) • Destructuring val (operation, user, hostname, groupId) = aclParams • Partially destructuring val (operation, _, hostname, _) = aclParams

Slide 31

Slide 31 text

Expressions • Evaluating an expression and assigning a variable val res = if (operation == CREATE) { createAcl(aclParameters) } else if (operation == ALTER) { alterAcl(aclParameters) } else if (operation == DELETE) { deleteAcl(aclParameters) } else { throw RuntimeException("Operation not supported: ${operation}") }

Slide 32

Slide 32 text

Streams • Remember this piece of Java code? List userAclBindings = aclBindings.stream() .filter(aclBinding -> aclBinding.entry().principal().equals(filterUser)) .collect(Collectors.toList()); • The same in Kotlin val userAclBindings = aclBindings.filter { it.entry().principal() == filterUser } Type inference No stream(), no Collector Implicit argument Replaces equals()

Slide 33

Slide 33 text

Streams • Java private List buildKafkaUris(List instances, String securityProtocol, String port) { return instances.stream() .map(i -> getTagOrFail(i, TAG_KEY_NAME)) .map(name -> buildUri(securityProtocol, name, port)) .collect(Collectors.toList()); } • Kotlin private fun buildKafkaUris(instances: List, securityProtocol: String, port: String): List { return instances .map { getTag(it, TAG_KEY_NAME) } .map { buildUri(securityProtocol, it, port) } } Null safety through types (see further)

Slide 34

Slide 34 text

Null safety • Kotlin tries to eliminate NullPointerExceptions • Suffix the type with ‘?’ to declare a nullable var/param var s: String? = null • Need to test for nulls before being able to use the value return s.length Error:(37, 17) Kotlin: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String? if (s !=null) { return s.length }

Slide 35

Slide 35 text

Null safety • Can’t return a potentially null value if not declared as nullable private fun getTag(instance: Instance, tagKey: String): String { val tag = instance.tags .filter { it.key.equals(tagKey, ignoreCase = true) } .map { it.value } .firstOrNull() return tag } Error:(40, 16) Kotlin: Type mismatch: inferred type is String? but String was expected • Safe: private fun getTag(instance: Instance, tagKey: String): String { val tag = instance.tags .filter { it.key.equals(tagKey, ignoreCase = true) } .map { it.value } .firstOrNull() if (tag == null) { throw RuntimeException("Tag [${tagKey}] not found") } return tag } • (Or just call first() in this case) tag is of type String? tag is of type String

Slide 36

Slide 36 text

Null safety • Elvis operator val hostname = aclParameters.hostname ?: “*” • Safe calls val length: Int? = s?.length Return value is a nullable type

Slide 37

Slide 37 text

Functions • First class support for functions protected fun withAdminClient(func: (AdminClient) -> R): R { val adminClient = adminClientFactory.createAdminClient() return adminClient.use { func(adminClient) } } A function with one param of type AdminClient Call it… as a function (Bye bye functional interfaces)

Slide 38

Slide 38 text

Type alias • Can alias any type typealias Clusters = List • Example typealias KakfaAdminFunc = (AdminClient) -> R protected fun withAdminClient(func: KakfaAdminFunc): R { …

Slide 39

Slide 39 text

Extension functions • Extend existing classes with new methods • Resolved statically / locally • Example fun AmazonEC2.findInstances(request: DescribeInstancesRequest): ArrayList { val allInstances = ArrayList() var token: String? do { val result = this.describeInstances(request) val instances = result.reservations.flatMap { it.instances } allInstances.addAll(instances) token = result.nextToken request.nextToken = token } while (token != null) return allInstances } ec2.findInstances(request) Class to extend Method

Slide 40

Slide 40 text

Wrapping up

Slide 41

Slide 41 text

Conclusion • Easy to use & intuitive • Greater expressivity than Java • Simpler than Scala • Very positive experience so far

Slide 42

Slide 42 text

Try it out! @aseigneurin