Kotlin

From zero to eleven

Jussi Hallila / @Xantier

Selling points

Beautiful Syntax

Succinct yet expressive
// file: Beaut.kt
data class Lunch(val bread: String, val topping: String)
val yumyum = Lunch("Toast", "Avocado")
fun main(args: Array<String>){
    val buyHouse: (Lunch) -> Unit = {
        val (_, topping) = it
        when(topping){
            "Avocado" -> println("Not a chance")
            else -> println("Hmmm... still probably no")
        }
    }
    buyHouse(yumyum)
}

// Not a chance

Superior Type Inference

Just enough

var str: String = "Text"
val str = "String"
fun sideEffects() {
    println("Returning Unit -> no need for type")
}
fun expressiveFunc() = "Return type String, as is known"
fun explicit(): String {
    return "Making the return type explicit"
}

Full Java Interoperability

public class Interop {
    public static void main(String[] args) {
        final Lunch lunch = BeautKt.getYumyum();
        System.out.println("I had "
        + lunch.getTopping()
        + " on "
        + lunch.getBread()
        + " for lunch.");
    }
}
// I had avocado on toast for lunch.

Null Safety


public String doSomething(String arg1, String arg2, ...){
	if (arg1 == null) {
 		throw new IllegalArgumentException("arg1 == null");
	}
	if (arg2 == null) {
 		throw new IllegalArgumentException("arg1 == null");
	}
	// Etc
	// Finally do something
}
					
java.lang.nullPointerException
	at com.something.doSomething(Something.java:42)
	at com.someframework.doThatThing(ThingDoer.java:13)
	at org.application.service.letsHopeThisWorks(HopeBuildsSoftware.java:666)
	...
	...s
					

Null Safety

class TopShelf(val drinks: List<String>?)

fun main(args: Array<String>){
    val monday = TopShelf(null)
    println(monday.drinks.get(0))
}
/*
* Error Kotlin: Only safe (?.) or non-null asserted (!!.)
* calls are allowed on a nullable receiver of type List<String>?
*/

                
println(monday.drinks?.get(0)) // This is grand
// null
                

Tooling

  • Jetbrains native citizen - Kotlin <3 IntelliJ
  • Native in Android Studio as well
  • Build processes click in place with Maven & Gradle (and Ant)
  • Plugins to Eclipse (official), Sublime, Atom, Vim
  • Magical intellisense out of the box

High level laws of Kotlin

Finality

  • Classes final by default
  • vals final by default
  • open keyword to make classes extendable

Organization

  • 1 file, multiple definitions
  • top-level, essentially global, functions & values
  • Decompiled to a class by filename and static vars/funcs

Visibility

  • Everything is public by default
  • Modifiers: private, internal, protected

Classes, Interfaces, Objects and Basic syntax

Classes, Interfaces and Objects

class EmptyClass
interface MarkerInterface
class WithConstructor(val value: String): MarkerInterface
class WithMembers(val value: String) {
    fun doFun() = "Fun!"
}
object Singleton {
    val staticity = "Not Quite, but close"
}
class Companied {
    companion object {
        val truly = "Static, truly static"
    }
} // Companied.truly

Classes, Interfaces and Objects

object initialized{
    val thing: String
    init {
        thing = "Hello"
    }
}
class LateBloomer{
    lateinit var thang: String
    fun doTheInit(str: String){
        thang = str
    }
    @Autowired
    lateinit var thong: String
    @Inject
    lateinit var gString: String
}

Data Classes


data class Covfefe(val first: String, val second: String)
data class -> toString, equals, hashCode, copy

val (america, finland) = Covfefe("America", "Finland")

Data Classes

public final class Covfefe {
   @NotNull
   private final String first;
   @NotNull
   private final String second;

   @NotNull
   public final String getFirst() {
      return this.first;
   }

   @NotNull
   public final String getSecond() {
      return this.second;
   }

   public Covfefe(@NotNull String first, @NotNull String second) {
      Intrinsics.checkParameterIsNotNull(first, "first");
      Intrinsics.checkParameterIsNotNull(second, "second");
      super();
      this.first = first;
      this.second = second;
   }

   @NotNull
   public final String component1() {
      return this.first;
   }

   @NotNull
   public final String component2() {
      return this.second;
   }

   @NotNull
   public final Covfefe copy(@NotNull String first, @NotNull String second) {
      Intrinsics.checkParameterIsNotNull(first, "first");
      Intrinsics.checkParameterIsNotNull(second, "second");
      return new Covfefe(first, second);
   }

   public String toString() {
      return "Covfefe(first=" + this.first + ", second=" + this.second + ")";
   }

   public int hashCode() {
      return (this.first != null?this.first.hashCode():0) * 31 + (this.second != null?this.second.hashCode():0);
   }

   public boolean equals(Object var1) {
      if(this != var1) {
         if(var1 instanceof Covfefe) {
            Covfefe var2 = (Covfefe)var1;
            if(Intrinsics.areEqual(this.first, var2.first) && Intrinsics.areEqual(this.second, var2.second)) {
               return true;
            }
         }

         return false;
      } else {
         return true;
      }
   }
}

Sealed Classes

sealed class NextBigThing
data class PiperChat(val pakistaniDenzel: String): NextBigThing()
data class PiedPiper(val weissmanScore: String): NextBigThing()

When matching

val matchable: NextBigThing = fetchBigThing()
when(matchable){
  is PiperChat -> println("Creepy Videochat")
  is PiedPiper -> println("New Internet")
  else -> println("nope")
}

when, if, try = expressions

val drivingForce = when(matchable){
    is PiperChat -> matchable.pakistaniDenzel
    is PiedPiper -> matchable.weissmanScore
}
val carDoors = if(matchable is PiperChat) {
    "--(ツ)--"
} else {
    "¯\\_(ツ)_/¯"
}
val payingRent = try {
    askJinYang()
} catch (ex: InsultException){
    // Swallow ex
    "aFat and aPoor"
}

Functions

Fun, Fun, Fun

val funType: (num: Int) -> Int = {num -> num * num}
val funType2: (Int) -> Int = {it * it}
val funfunType: (Int, (Int) -> Int) -> Int = { num, func ->
    func(num) * func(num)
}
inline fun <S, reified T> inliner(other: S, typed: T) {
    if (other is T) {
        print("Same Types")
    }
}            

Infix and operator functions

data class PiperChat(val pakistaniDenzel: String): NextBigThing() {
    operator fun plus(internet: PiedPiper) = when(pakistaniDenzel){
        "fired" -> internet
        else -> this
    }
}
sealed class NextBigThing {
    infix fun until(num: Int) = when(this) {
        is PiedPiper -> weissmanScore.toInt() - num
        is PiperChat -> 2 - num
    }
}
val games = PiedPiper("23")
var eye = 2
val `fun` = PiperChat("Dinesh")
val scoreOrEyeCount: Int = `fun` + games until --eye
            

Extension functions

  • Adding functionality to existing classes
  • Removes the need for util classes completely
  • Resolved statically
  • Can't be used to override existing funs

Lambdas

Lambdas

val lamdba = { arg: Int ->
    arg * arg
}
/* val lambda = */ {it: Int -> it * it }
fun macbook(func: () -> Unit) = func()
macbook({ println("trash") })
macbook{ println("trash") }
fun isTrashbook(make: Int, func: (Int) -> Boolean): Boolean =
    func(make)
val isTrash = isTrashbook(2016) {
    it == 2016
}            

Collections & Extensions

Collections

  • Wrappers around Java collections
  • Mutable and Immutable versions
  • Sequences for lazy evaluated collections
  • Usual suspects:
    • Lists
    • Arrays
    • Sets
    • Maps

Collection Extensions

  • Maps, Filters, Reducers, FlatMaps, Zippers
  • Addition and Subtraction copies of immutables
  • Predicates to check collection state
  • Takes, Drops, Finds, First/Last for accessing elements
  • etc.

Extension functions

Extensions

  • Possible to have a nullable receiver with ?
  • Also extension properties
  • Can be declared as members of a class
  • Can be used as a function argument

Extension examples

fun String.uberized() = this + ", bro"
println("what's up".uberized()) // what's up, bro 

fun String?.uberized() = (this ?: "No way") + ", mo"
val str: String? = null
println(str.uberized()) // No way, mo

val String.fine: String
    get() = "a dumpster fire"
fun String.`is`(situation: String) = this + "is $fine"
println("uber".`is`("".fine)) // uber is a dumpster fire

Lambdas with receivers

From stdLib

fun <T, R> T.let(f: (T) -> R): R = f(this)
// nullableVar?.let { print(it.someValue) }
fun <T> T.apply(f: T.() -> Unit): T { f(); return this }
// javaBean.apply { this.beanField1 = "First field" } 
fun <T, R> with(receiver: T, f: T.() -> R): R = receiver.f()
// with(imperativo) { dothis() doThat() }
fun <T, R> T.run(f: T.() -> R): R = f()
// "str".run { this + "otherStr" } 
fun <T> T.also(block: (T) -> Unit): T { block(this); return this }
// javaBean.also { it.beanField2 = "Second Field" }  

Typesafe DSLs

Scaffolding

data class Pizza(val name: String,
    var toppings: List<Topping> = emptyList(),
    val price: Int = 0)

interface Topping {
    val name: String
    val unitPrice: Int
}

abstract class BaseTopping(override val name: String,
    override val unitPrice: Int = 0): Topping {
    var amount: Int = 0
    operator fun plus(that: Topping): List<Topping> =
        listOf(this, that)
}
            

Typesafe DSLs

Scaffolding

class Cheese: BaseTopping("Cheese") {
    override val unitPrice: Int
        get() = 3
}
class Ananas: Topping("Ananas"){
    override val unitPrice = 4
}
class Anchovy: Topping("Anchovy"){
    override val unitPrice = 6
}            

Typesafe DSLs

Construction

fun Pizza.`with`(topper: () -> List<Topping>): Pizza =
    apply { toppings = topper() }

infix fun Int.of(topping: BaseTopping): BaseTopping =
    topping.also { it.amount = this }
                
fun order(name: String, hoef: Pizza.() -> Pizza) =
    Pizza(name).hoef()

Typesafe DSLs

fun main(args: Array<String>) {
    order("Kotlinante") { // (name, Pizza.() -> Pizza) -> Pizza
        `with` { // this == our Kotlinante pizza
            (3 of Cheese()) +
            (2 of Anchovy()) +
            (2 of Ananas())
        // listOf(Cheese(amount=3), Anchovy(amt=2), Ananas(amt=2))
        }
    }
}
// Pizza(name=Kotlinante, toppings=[Cheese@279f2327,
// Anchovy@2ff4acd0, Ananas@54bedef2], price=0)

Delegates

Delegates

  • Built-in delegation pattern
  • Can be used to implement interfaces
  • Property getting and setting by delegation

Standard library helpers

class Sloth {
    val timeToSleep: Boolean by lazy {
        println("Always")
        true
    }
}
class NosyNeighbour {
    var shoppingTrip: Groceries by observable(Groceries()) {
        _, previousGroceries: Groceries, newGroceries: Groceries ->
            println("""Again he bought more craft beer.
    I believe he is a beer connoisseur, not an alcoholic.""")
    }  // property: KProperty<*>
}
class Wifey {
    var shoppingTrip: Groceries by vetoable(Groceries()) {
        _, previousGroceries: Groceries, newGroceries: Groceries ->
        !newGroceries.containsBeer()
    }
}
 

Manual delegation

Properties

data class Pizza(val name: String,
    var toppings: List<Topping> = emptyList()) {
        val price: Int by PriceCalculator()
        override fun toString() =
            "name=$name, price=$price, toppings=$toppings"
}
class PriceCalculator : ReadOnlyProperty<Pizza, Int> {
    override fun getValue(thisRef: Pizza, property: KProperty<*>) =
        thisRef.toppings.fold(0) { acc, it ->
            acc + it.unitPrice
        }
}
// Pizza(name=Kotlinante, toppings=[Cheese@3caeaf62,
// Anchovy@e6ea0c6, Ananas@6a38e57f], price=13)                 

Manual delegation

Classes

interface Topping {
    val name: String
    val unitPrice: Int
}
abstract class BaseTopping(val priceProvider: Topping)
    : Topping by priceProvider {
    var amount: Int = 0
    operator fun plus(that: Topping): List<Topping> =
        listOf(this, that)
    override fun toString() =
        "{name=$name, price=$unitPrice, amount=$amount}"
}                 

Class level delegation

class PriceAndNameProvider(override val name: String,
    override val unitPrice: Int): Topping
class Cheese : BaseTopping(PriceAndNameProvider("Cheese", 3))
class Ananas : BaseTopping(PriceAndNameProvider("Ananas", 4))
class Anchovy : BaseTopping(PriceAndNameProvider("Anchovy", 6))

val BaseTopping.price: Int
    get() = unitPrice * amount
// { acc, it ->
//    acc + (it as BaseTopping).price
// } 

Typesafe delegated higher order lambdas with receivers DSL

fun main(args: Array<String>) {
    order("Kotlinante") {
        `with` {
            (3 of Cheese()) +
            (2 of Anchovy()) +
            (2 of Ananas())
        }
    }
}
// name=Kotlinante, price=29, toppings=
//   [{name=Cheese, price=3, amount=3},
//    {name=Anchovy, price=6, amount=2},
//    {name=Ananas, price=4, amount=2}]

Kotlin 1.1

  • Type aliases
  • Coroutines
  • Javascript support

How Do I sell this beauty to the management?

  • Java interoperability
  • Safety
  • Cheap investment
  • Easeness

Java interoperability

All already familiar frameworks work out of the box.
Many frameworks!

Safety

The Compiler helps us a lot more than in Java.
Safe like a wall!

Cheap investment

It just works.
There is no need to rewrite anything, existing codebase can remain untouched and adding it to a project is literally a three click operation.
Cheap!

Easeness

If you know Java, you'll learn Kotlin in few days, guaranteed.
So easy!

It is more fun

Once you go Kotlin,

your fun will be Dublin

TY

Slides / Code examples