Kotlin

Serban Iordache - 29.03.2018
version 1.0.0

What is Kotlin?

"Statically typed programming language for modern multiplatform applications"
https://kotlinlang.org/

Target platforms

  • JVM

    • server-side (web apps, backends of mobile apps, microservices etc.)
    • client-side (Swing, JavaFX, console apps etc.)
    • scripting (ex.: build.gradle.kts)

  • Android

    • official language since May 2017

  • JavaScript

    • client-side (browser)
    • server-side (node.js)

  • Native

    • via LLVM (Windows, Linux, MacOS, iOS, Android, WebAssembly)

JVM: build process

build process

Hello World

fun main(args: Array<String>) {

    println("Hello, world!")
}
  • top-level function
  • parameter type written after its name
  • println instead of System.out.println
  • semicolons are optional

Hello World

fun main(args: Array<String>) {
    val name = if(args.size > 0) args[0] else "world"
    println("Hello, $name!")
}
  • use val for immutable variables
  • control structures such as if and when are expressions
  • the compiler can infer the variable type
  • string templates

Hello World

fun main(args: Array<String>) {
    val name = if(args.size > 0) args[0] else "world"
    val sender = if(args.size > 1) args[1] else "not important"
    println("""Hello, $name,
        |My name is $sender.
        |I need your help.
    """.trimMargin())
}
  • multi-line strings

Hello World

fun main(args: Array<String>) {
    val name = if(args.size > 0) args[0] else "world"
    var sender = "not important"
    if(args.size > 1) sender = args[1]
    println("""Hello, $name,
        |My name is $sender.
        |I need your help.
    """.trimMargin())
}
  • use var for mutable variables

Loops

for(i in 1 .. 10) { // 1 2 3 4 5 6 7 8 9 10
    print(" $i")
}
for(i in 1 until 10) { // 1 2 3 4 5 6 7 8 9
    print(" $i")
}
for(i in 10 downTo 1 step 2) { // 10 8 6 4 2
    print(" $i")
}
var j = 0
while(j++ < 5) print(" $j") // 1 2 3 4 5
var k = 0
do print(" $k") while(k++ < 5) // 0 1 2 3 4 5

when

fun display(input: Any) {
    when(input) {
        10 -> println("ten")
        11, 12 -> println("eleven or twelve")
        in 1 .. 9 -> println("a single-digit number")
        is String -> println("a string with length ${input.length}")
        else -> println("something else")
    }
}
...
...
...
    display(10)                  // ten
    display(12)                  // eleven or twelve
    display(5)                   // a single-digit number
    display("Hello")             // a string with length 5
    display(42)                  // something else
    display(RuntimeException())  // something else

Functions

fun isPalindrome(s: String): Boolean {
    return s == s.reversed()
}

fun main(args: Array<String>) {
    println(isPalindrome("dehydrated"))   // false
    println(isPalindrome("detartrated"))  // true
}
  • == calls equals()
  • === compares references

Functions

fun isPalindrome(s: String) = s == s.reversed()



fun main(args: Array<String>) {
    println(isPalindrome("dehydrated"))   // false
    println(isPalindrome("detartrated"))  // true
}
  • function with an expression body
  • return type inference

Functions

fun String.isPalindrome() = this == this.reversed()



fun main(args: Array<String>) {
    println("dehydrated".isPalindrome()) // false
    println("detartrated".isPalindrome())  // true
}
  • extension function

Functions

import java.util.*
import kotlin.math.PI

fun write(value: Double, decimals: Int,
          prefix: String, suffix: String, locale: Locale) {
    println("${prefix}%.${decimals}f${suffix}".format(locale, value))
}

fun main(args: Array<String>) {
    write(PI, 5, "length = ",
            " cm.", Locale.GERMANY)           // length = 3,14159 cm.
    write(PI, 4, "", " cm.", Locale.US)       // 3.1416 cm.
    write(PI, 2, "", "", Locale.GERMANY)      // 3,14
    write(PI, 2, "", "", Locale.US)           // 3.14
}
  • positional parameters

Functions

import java.util.*
import kotlin.math.PI

fun write(value: Double, decimals: Int = 2,
          prefix: String = "", suffix: String = "", locale: Locale = Locale.US) {
    println("${prefix}%.${decimals}f${suffix}".format(locale, value))
}

fun main(args: Array<String>) {
    write(PI, 5, "length = ",
            " cm.", Locale.GERMANY)           // length = 3,14159 cm.
    write(PI, 4, "", " cm.")                  // 3.1416 cm.
    write(PI, 2, "", "", Locale.GERMANY)      // 3,14
    write(PI)                                 // 3.14
}
  • default parameters

Functions

import java.util.*
import kotlin.math.PI

fun write(value: Double, decimals: Int = 2,
          prefix: String = "", suffix: String = "", locale: Locale = Locale.US) {
    println("${prefix}%.${decimals}f${suffix}".format(locale, value))
}

fun main(args: Array<String>) {
    write(PI, locale = Locale.GERMANY, prefix = "length = ",
            suffix = " cm.", decimals = 5)   // length = 3,14159 cm.
    write(PI, decimals = 4, suffix = " cm.") // 3.1416 cm.
    write(PI, locale = Locale.GERMANY)       // 3,14
    write(PI)                                // 3.14
}
  • mixing positional and named parameters

Access modifiers

Modifier Corresponding member Comments

final

Can’t be overridden

Used by default for class members

open

Can be overridden

Should be specified explicitly

abstract

Must be overridden

Can be used only in abstract classes; abstract members can’t have an implementation

override

Overrides a member in a superclass or interface

Overridden member is open by default, if not marked final

Visibility modifiers

Modifier Class member Top-level declaration

public (default)

Visible everywhere

Visible everywhere

internal

Visible in a module

Visible in a module

protected

Visible in subclasses

-

private

Visible in a class

Visible in a file

  • module = a set of Kotlin files compiled together
  • there is no package-private visibility

Classes

Java
public class Person {
    private final String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}
Kotlin
class Person(val name: String, var age: Int)

Classes

class Person(val name: String, var age: Int)

fun main(args: Array<String>) {
    val p = Person("Jim", 33)
    println(p.name)
}
  • Person is a value object
  • classes are public and final by default
  • you call the constructor directly, without the new keyword
  • name and age are properties

Properties

class Person(val name: String, var age: Int) {
    val isAdult: Boolean
        get() = age >= 18
}

fun main(args: Array<String>) {
    val p = Person("Jim", 33)
    println(p.isAdult) // true
    p.age = 15
    println(p.isAdult) // false
}
  • custom getter

Properties

class Person(val name: String, var age: Int) {
    var jobs = 0
        set(value) {
            field = when {
                value < 0 -> 0
                value > 3 -> 3
                else -> value
            }
        }
}

fun main(args: Array<String>) {
    val p = Person("Jim", 33)
    p.jobs = 1
    println(p.jobs) // 1
    p.jobs = -5
    println(p.jobs) // 0
    p.jobs = 7
    println(p.jobs) // 3
}
  • custom setter
  • use the special identifier field to access the backing field

Data classes

Java
public class Person {
    private final String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &&
                Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    public Person copy() {
        return new Person(name, age);
    }

    @Override
    public String toString() {
        return "Person(name=" + name + ", age=" + age + ")";
    }
}
Kotlin
data class Person(val name: String, var age: Int)

Data classes

data class Person(val name: String, var age: Int)

fun main(args: Array<String>) {
    val p1 = Person("Jim", 33)
    val p2 = p1.copy()
    println(p1)        // Person(name=Jim, age=33)
    println(p2)        // Person(name=Jim, age=33)
    println(p1 == p2)  // true
    println(p1 === p2) // false

    val p3 = p1.copy(age=17)
    println(p3)        // Person(name=Jim, age=17)
}

Nested classes

Class A declared within another class B In Java In Kotlin

Nested class (doesn’t store a reference to an outer class)

class B {
  static class A {}
}
class B {
  class A
}

Inner class (stores a reference to an outer class)

class B {
  class A {}
}
class B {
  inner class A
}
nested class

Sealed classes

sealed class Alert
class Email(val recipient: String, val title: String, val body: String) : Alert()
class SMS(val cc: Int, val ndc: Int, val sn: Int, val message: String) : Alert()

fun getAckMessage(alert: Alert) =
    when(alert) {
        is Email -> "Email with title '${alert.title}' sent to ${alert.recipient}: ${alert.body}"
        is SMS -> "SMS sent to ++(${alert.cc})${alert.ndc}-${alert.sn}: ${alert.message}"
    }


fun main(args: Array<String>) {
    println(getAckMessage(Email("nobody@nowhere.net", "Epic fail", "Your server exploded")))
        // Email with title 'Epic fail' sent to nobody@nowhere.net: Your server exploded

    println(getAckMessage(SMS(49, 171, 47110815, "Godzilla is back in town")))
        // SMS sent to ++(49)171-47110815: Godzilla is back in town
}
  • use sealed classes to represent constrained hierarchies in which an object can only be of one of the given types
  • the subclasses of a sealed class must be declared in the same file as the sealed class itself

Constructors

open class User(val nickname: String)                // primary constructor
class TwitterUser(nickname: String) : User(nickname) // calling parent's primary constructor
open class Button                                    // a default constructor is generated
class RadioButton: Button()                          // calling parent's default constructor
class MenuItem(val name: String,
               var enabled: Boolean=true,
               var checked: Boolean=false)           // constructor with default arguments
class Secretive private constructor()                // private primary constructor
class Point(val x: Int, val y: Int)

class Rectangle(val x1: Int, val y1: Int, val x2: Int, val y2: Int) { // primary constructor
    constructor(p1: Point, p2: Point): this(p1.x, p1.y, p2.x, p2.y)   // secondary constructor
}

Constructors

class Point(val x: Int, val y: Int)

class Rectangle {                       // no primary constructor
    val x1: Int
    val y1: Int
    val x2: Int
    val y2: Int
    constructor(p1: Point, p2: Point) { // secondary constructor
        x1 = p1.x
        y1 = p1.y
        x2 = p2.x
        y2 = p2.y
    }
}

Constructors

class Point(val x: Int, val y: Int)

class Rectangle(p1: Point, p2: Point) { // primary constructor
    val x1: Int
    val y1: Int
    val x2: Int
    val y2: Int
    init {                              // initializer block
        x1 = p1.x
        y1 = p1.y
        x2 = p2.x
        y2 = p2.y
    }
}

Objects

class MyLogger {
    val context = mutableMapOf<String,String>()
    fun log(message: String) {
        println("$context: $message")
    }
}

val Logger = MyLogger()

fun main(args: Array<String>) {
    Logger.context["thread"] = "worker-7"
    Logger.context["txID"] = "dead-b055-1234"
    Logger.log("Account successfully deleted")
    // Output: {thread=worker-7, txID=dead-b055-1234}: Account successfully deleted
}
  • there is no static keyword in Kotlin
  • Logger is a top-level property

Objects

object Logger {
    val context = mutableMapOf<String,String>()
    fun log(message: String) {
        println("$context: $message")
    }
}



fun main(args: Array<String>) {
    Logger.context["thread"] = "worker-7"
    Logger.context["txID"] = "dead-b055-1234"
    Logger.log("Account successfully deleted")
    // Output: {thread=worker-7, txID=dead-b055-1234}: Account successfully deleted
}
  • object provides support for creating singletons
  • it combines a class declaration and a declaration of a single instance of that class

Objects

data class Person(val name: String) {
    object NameComparator : Comparator<Person> {
        override fun compare(p1: Person, p2: Person) = p1.name.compareTo(p2.name)
    }
}

fun main(args: Array<String>) {
    val persons = listOf(Person("Bob"), Person("Alice"))
    println(persons.sortedWith(Person.NameComparator))
    // Output: [Person(name=Alice), Person(name=Bob)]
}
  • object can appear inside classes
  • object declarations can inherit from classes and interfaces

Companion objects

import kotlin.math.*

data class Point (val x: Double, val y: Double)

fun fromPolar(r: Double, phi: Double) = Point(r * cos(phi), r * sin(phi))




fun main(args: Array<String>) {
    println(Point(3.0, 4.0))
    println(fromPolar(5.0, PI / 3))
}
  • fromPolar should belong to Point

Companion objects

import kotlin.math.*

data class Point private constructor(val x: Double, val y: Double) {
    object Factory {
        fun fromCartesian(x: Double, y: Double) = Point(x, y)
        fun fromPolar(r: Double, phi: Double) = Point(r * cos(phi), r * sin(phi))
    }
}

fun main(args: Array<String>) {
    println(Point.Factory.fromCartesian(3.0, 4.0))
    println(Point.Factory.fromPolar(5.0, PI / 3))
}
  • how can we get rid of the object name (Factory)?

Companion objects

import kotlin.math.*

data class Point private constructor(val x: Double, val y: Double) {
    companion object {
        fun fromCartesian(x: Double, y: Double) = Point(x, y)
        fun fromPolar(r: Double, phi: Double) = Point(r * cos(phi), r * sin(phi))
    }
}

fun main(args: Array<String>) {
    println(Point.fromCartesian(3.0, 4.0))
    println(Point.fromPolar(5.0, PI / 3))
}
  • only one companion object per class

Exceptions

  • Kotlin doesn’t differentiate between checked and unchecked exceptions
  • you don’t specify the exceptions thrown by a function
  • you can use throw as an expression
fun getEmailUser(email: String): String {
    val user =
            if(email.contains('@'))
                email.substringBefore('@')
            else
                throw IllegalArgumentException()
    return user.toLowerCase()
}

fun main(args: Array<String>) {
    println(getEmailUser("John.Smith@example.org")) // john.smith
    println(getEmailUser("I have no email"))        // throws IllegalArgumentException
}

Exceptions

  • you can use try as an expression
fun getEmailUser(email: String): String {
    val user = if(email.contains('@')) email.substringBefore('@')
               else throw IllegalArgumentException()
    return user.toLowerCase()
}

fun printEmailUser(email: String) {
    val user = try {
        getEmailUser(email)
    } catch(e: Exception) {
        return
    }
    println(user)
}

fun main(args: Array<String>) {
    printEmailUser("John.Smith@example.org")        // john.smith
    printEmailUser("I have no email")               // nothing is printed
}

Exceptions

  • you can use try as an expression
fun getEmailUser(email: String): String {
    val user = if(email.contains('@')) email.substringBefore('@')
               else throw IllegalArgumentException()
    return user.toLowerCase()
}

fun printEmailUser(email: String) {
    val user = try {
        getEmailUser(email)
    } catch(e: Exception) {
        "unknown"
    }
    println(user)
}

fun main(args: Array<String>) {
    printEmailUser("John.Smith@example.org")        // john.smith
    printEmailUser("I have no email")               // unknown
}

Nullability

fun strLen(s: String): Int = s.length

fun main(args: Array<String>) {
    println(strLen("Hello"))  // 5
//    println(strLen(null))   // compile error: Null can not be a value of a non-null type String
}
// fun strLenSafe(s: String?): Int = s.length // compile error: Only safe (?.) or
//             non-null asserted (!!.) calls are allowed on a nullable receiver of type String?
fun strLenSafe(s: String?): Int = if(s != null) s.length else 0

fun main(args: Array<String>) {
    println(strLenSafe("Hello"))  // 5
    println(strLenSafe(null))     // 0
}

Nullability - safe call operator

class Address(val streetAddress: String, val zipCode: Int, val city: String, val country: String)

class Company(val name: String, val address: Address?)

class Person(val name: String, val company: Company?)

fun Person.countryName() = this.company?.address?.country ?: "Unknown"

fun main(args: Array<String>) {
    val michael = Person("Michael", Company("SCOOP",
            Address("Eiler Straße 3P", 51107, "Köln", "Germany")))
    val dilbert = Person("Dilbert", null)
    println(michael.countryName())  // Germany
    println(dilbert.countryName())  // Unknown
}

Nullability - not-null assertion

class Address(val streetAddress: String, val zipCode: Int, val city: String, val country: String)

class Company(val name: String, val address: Address?)

class Person(val name: String, val company: Company?)

fun Person.countryName() = this.company!!.address!!.country  // unsafe

fun main(args: Array<String>) {
    val michael = Person("Michael", Company("SCOOP",
            Address("Eiler Straße 3P", 51107, "Köln", "Germany")))
    val dilbert = Person("Dilbert", null)
    println(michael.countryName())  // Germany
    println(dilbert.countryName())  // throws KotlinNullPointerException
}

Nullability - extensions for nullable types

class Address(val streetAddress: String, val zipCode: Int, val city: String, val country: String)
class Company(val name: String, val address: Address?)
class Person(val name: String, val company: Company?)

fun Person?.hasEmployer() = (this != null) && (this.company != null)

fun checkEmployment(p: Person?) {
    println(if(p.hasEmployer()) "employed" else "unemployed")
}

fun main(args: Array<String>) {
    val michael = Person("Michael", Company("SCOOP",
            Address("Eiler Straße 3P", 51107, "Köln", "Germany")))
    val dilbert = Person("Dilbert", null)
    checkEmployment(michael)  // employed
    checkEmployment(dilbert)  // unemployed
    checkEmployment(null)     // unemployed
}

Nullability - safe cast operator

class Person(val firstName: String, val lastName: String) {
    override fun equals(o: Any?): Boolean {
        val otherPerson = o as? Person ?: return false
        return otherPerson.firstName == firstName &&
                otherPerson.lastName == lastName
    }

    override fun hashCode(): Int =
            firstName.hashCode() * 37 + lastName.hashCode()
}

fun main(args: Array<String>) {
    val p1 = Person("John", "Smith")
    val p2 = Person("John", "Smith")

    println(p1.equals(p2)) // true
    println(p1.equals(42)) // false
}

Platform types

// Java
public class User {
    private final String id;
    private String email;

    public User(String id) { this.id = java.util.Objects.requireNonNull(id); }

    public String getId() { return id; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
}
// Kotlin
fun main(args: Array<String>) {
    val u = User("jsmith")
    println(u.id?.toUpperCase())    // JSMITH
    println(u.email?.toUpperCase()) // null

    println(u.id.toUpperCase())    // JSMITH
    println(u.email.toUpperCase()) // java.lang.IllegalStateException: u.email must not be null
}

Platform types

// Java
public class User {
    private final String id;
    private String email;

    public User(String id) { this.id = java.util.Objects.requireNonNull(id); }

    public String getId() { return id; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
}
// Kotlin
class UpperUser(id: String): User(id) {
    override fun getId(): String = super.getId().toUpperCase()

    override fun getEmail(): String? = super.getEmail()?.toUpperCase()
    override fun setEmail(email: String?) {
        super.setEmail(email?.toUpperCase())
    }
}

Primitive and other basic types

  • Kotlin doesn’t distinguish between primitive types and wrapper types
val i: Int = 1
val list: List<Int> = listOf(1, 2, 3)
  • at runtime, the number types are represented in the most efficient way possible
  • Kotlin types that correspond to Java primitive types:

    • Integer types: Byte, Short, Int, Long
    • Floating-point number types: Float, Double
    • Character type: Char
    • Boolean type: Boolean

Primitive and other basic types

Kotlin Java Comments Code

Any

Object

the supertype of all non-nullable types in Kotlin

val answer: Any = 42

Unit

Void

can be also used as a type argument

interface Processor<T> { fun process(): T }
class NoResultProcessor : Processor<Unit> {
    override fun process() { /* do stuff */ }
}

Nothing

-

used as return type for functions that never complete successfully

fun fail(message: String): Nothing {
    throw Exception(message)
}
...
val addr = user.address ?: fail("No address")
println(addr.city)

Class delegation

import java.sql.Connection
import java.sql.ResultSet

interface QueryExecutor {
    fun execute(sql: String): ResultSet
}

class QueryExecutorImpl(val connectionProvider: () -> Connection): QueryExecutor {
    override fun execute(sql: String): ResultSet =
            connectionProvider().createStatement().executeQuery(sql)
}








// Change request: the returned ResultSet should mask the values from password-containing columns

Class delegation

import java.sql.Connection
import java.sql.ResultSet

interface QueryExecutor {
    fun execute(sql: String): ResultSet
}

class QueryExecutorImpl(val connectionProvider: () -> Connection): QueryExecutor {
    override fun execute(sql: String): ResultSet =
            SafeResultSet(connectionProvider().createStatement().executeQuery(sql))
}

class SafeResultSet(val rs: ResultSet) : ResultSet by rs {
    override fun getString(columnLabel: String?): String =
            if(columnLabel!!.toLowerCase().contains("password")) "***"
            else rs.getString(columnLabel)

    override fun getString(columnIndex: Int): String =
            getString(rs.metaData.getColumnLabel(columnIndex))
}

Property delegation

object CountryTable : IntIdTable() {
    val name = varchar("name", 250).uniqueIndex()
    val iso = varchar("iso", 2).uniqueIndex()
}

class Country(id: EntityID<Int>) : IntEntity(id) {
    companion object : IntEntityClass<Country>(CountryTable)
    var name: String by CountryTable.name
    var iso: String by CountryTable.iso
}

fun main(args: Array<String>) {
    Database.connect("jdbc:h2:mem:test", driver = "org.h2.Driver")
    transaction {
        logger.addLogger(StdOutSqlLogger)
        create (CountryTable)
        Country.new { name = "Germany"; iso = "de" }
        Country.new { name = "Russia"; iso = "ru" }
        Country.new { name = "United States"; iso = "us" }
        val russia = Country.find { CountryTable.iso.eq("ru") }.first()
        println(russia.name)    // Russia
    }
}

Lazy initialization

class Email {/* ... */}

fun loadEmails(person: Person): List<Email> {
    println("Load emails for ${person.name}")
    return listOf(/*...*/)
}

class Person(val name: String /* ... */) {

    val emails: List<Email> = loadEmails(this)
}







// emails will be loaded even if they are not required

Lazy initialization

class Email {/* ... */}

fun loadEmails(person: Person): List<Email> {
    println("Load emails for ${person.name}")
    return listOf(/*...*/)
}

class Person(val name: String /* ... */) {
    private var nullableEmails: List<Email>? = null
    val emails: List<Email>
        get() {
            if (nullableEmails == null) {
                nullableEmails = loadEmails(this)
            }
            return nullableEmails!!
        }
}

// not thread-safe

Lazy initialization

class Email {/* ... */}

fun loadEmails(person: Person): List<Email> {
    println("Load emails for ${person.name}")
    return listOf(/*...*/)
}

class Person(val name: String /* ... */) {

    val emails by lazy { loadEmails(this) }
}







// lazy is a special case of property delegation

Collections

  • Kotlin builds on the Java collections library
  • new features are added through extension functions
fun readNumbers(reader: BufferedReader): List<Int?> {
    val result = ArrayList<Int?>()
    for (line in reader.lineSequence()) {
        result.add(line.toIntOrNull())
    }
    return result
}
fun addValidNumbers(numbers: List<Int?>) {
    val validNumbers = numbers.filterNotNull()
    println("Sum of valid numbers: ${validNumbers.sum()}")
    println("Invalid numbers: ${numbers.size - validNumbers.size}")
}

Collections

list 1 1
List<Int>

 
(List of Ints)

list 1 0
List<Int?>

 
(List of nullable Ints)

list 0 1
List<Int>?

 
(Nullable list of Ints)

list 0 0
List<Int?>?

 
(Nullable list of nullable Ints)

Collections

  • Kotlin separates interfaces for accessing the data in a collection and for modifying the data
  • every Java collection interface has a read-only and a mutable representation in Kotlin
collections

Collections

Collection-creation functions

Collection type Read-only Mutable

List

listOf

mutableListOf, arrayListOf

Set

setOf

mutableSetOf, hashSetOf, linkedSetOf, sortedSetOf

Map

mapOf

mutableMapOf, hashMapOf, linkedMapOf, sortedMapOf

 

fun main(args: Array<String>) {
    val mlist = mutableListOf(3, 6, 7)
    mlist[0] = 1
    println(mlist)                        // [1, 6, 7]

    val list = listOf(3, 6, 7)
//    list[0] = 1                         // doesn't compile
    println(list.filter { it % 2 == 1 })  // [3, 7]

    val map = mutableMapOf(1 to "Alice", 2 to "Bob")
    println(map[1]?.toUpperCase())        // ALICE
}

Arrays

val numbers = arrayOf(1, 2, 3, 4, 5)
println(numbers.toList())                     // [1, 2, 3, 4, 5]
val nulls = arrayOfNulls<Int>(4)
println(nulls.joinToString())                 // null, null, null, null
val bytes = byteArrayOf(83, 99, 111, 111, 112)
println(String(bytes))                        // Scoop
val squares = IntArray(5) { i -> (i+1) * (i+1) }
println(squares.joinToString())               // 1, 4, 9, 16, 25
val letters = Array<String>(26) { i -> ('a' + i).toString() }
println(letters.joinToString(""))             // abcdefghijklmnopqrstuvwxyz
val names = arrayOf("Alice", "Bob", "Carol")
names.forEachIndexed { index, name ->         // 1: Alice
    println("${index+1}: $name")              // 2: Bob
}                                             // 3: Carol

vararg & spread

fun max(vararg numbers: Int) =
        numbers.reduce{max, e -> if(max > e) max else e}
...
...
...
    println(max(5, 3, 7, 2))                     // 7
    val values = intArrayOf(6, 9, 1, 8)
    println(max(5, 3, *values, 7, 2))            // 9

    val strings = listOf("a", "b", "c")
    println("%s/%s/%s".format(*strings.toTypedArray())) // a/b/c

Infix calls

class Person(val name: String, var spouse: Person? = null) {
    fun marry(p: Person) {
        spouse = p
        p.spouse = this
        println("$name is now married to ${p.name}")
    }
}

fun main(args: Array<String>) {
    val john = Person("John")
    val anna = Person("Anna")
    john.marry(anna)  // John is now married to Anna
}

Infix calls

class Person(val name: String, var spouse: Person? = null) {
    infix fun marry(p: Person) {
        spouse = p
        p.spouse = this
        println("$name is now married to ${p.name}")
    }
}

fun main(args: Array<String>) {
    val john = Person("John")
    val anna = Person("Anna")
    john marry anna   // John is now married to Anna
}

Infix calls

val s1 = setOf("aaa", "bbb")
val s2 = setOf("bbb", "ccc")
println(s1.union(s2))   // [aaa, bbb, ccc]

Infix calls

val s1 = setOf("aaa", "bbb")
val s2 = setOf("bbb", "ccc")
println(s1.union(s2))   // [aaa, bbb, ccc]
println(s1 union s2)    // [aaa, bbb, ccc]

Infix calls

val s1 = setOf("aaa", "bbb")
val s2 = setOf("bbb", "ccc")
println(s1.union(s2))   // [aaa, bbb, ccc]
println(s1 union s2)    // [aaa, bbb, ccc]
for(i in 1 until 10 step 2) print("$i ")   // 1 3 5 7 9

Infix calls

val s1 = setOf("aaa", "bbb")
val s2 = setOf("bbb", "ccc")
println(s1.union(s2))   // [aaa, bbb, ccc]
println(s1 union s2)    // [aaa, bbb, ccc]
for(i in 1 until 10 step 2) print("$i ")   // 1 3 5 7 9
for(i in 1.until(10).step(2)) print("$i ") // 1 3 5 7 9

Infix calls

val s1 = setOf("aaa", "bbb")
val s2 = setOf("bbb", "ccc")
println(s1.union(s2))   // [aaa, bbb, ccc]
println(s1 union s2)    // [aaa, bbb, ccc]
for(i in 1 until 10 step 2) print("$i ")   // 1 3 5 7 9
for(i in 1.until(10).step(2)) print("$i ") // 1 3 5 7 9
println(mapOf(1 to "Alice", 2 to "Bob"))   // {1=Alice, 2=Bob}

Infix calls

val s1 = setOf("aaa", "bbb")
val s2 = setOf("bbb", "ccc")
println(s1.union(s2))   // [aaa, bbb, ccc]
println(s1 union s2)    // [aaa, bbb, ccc]
for(i in 1 until 10 step 2) print("$i ")   // 1 3 5 7 9
for(i in 1.until(10).step(2)) print("$i ") // 1 3 5 7 9
println(mapOf(1 to "Alice", 2 to "Bob"))   // {1=Alice, 2=Bob}
println(mapOf(1.to("Alice"), 2.to("Bob"))) // {1=Alice, 2=Bob}

Destructuring

import kotlin.math.*

data class Point(val x: Double, val y: Double)



fun fromPolar(r: Double, phi: Double) = Point(r * cos(phi), r * sin(phi))

fun main(args: Array<String>) {
    val p1 = fromPolar(4.0, PI / 5)
    val p2 = fromPolar(3.0, 7 * PI / 10)
    val dx = p2.x - p1.x
    val dy = p2.y - p1.y
    val d = sqrt(dx * dx + dy * dy)
    println("distance = $d")  // distance = 5.0
}

 

Destructuring

import kotlin.math.*

data class Point(val x: Double, val y: Double)



fun fromPolar(r: Double, phi: Double) = Point(r * cos(phi), r * sin(phi))

fun main(args: Array<String>) {
    val (x1, y1) = fromPolar(4.0, PI / 5)
    val (x2, y2) = fromPolar(3.0, 7 * PI / 10)
    val dx = x2 - x1
    val dy = y2 - y1
    val d = sqrt(dx * dx + dy * dy)
    println("distance = $d")  // distance = 5.0
}

Data classes can be destructured directly.

Destructuring

import kotlin.math.*

class Point(val x: Double, val y: Double) {
    operator fun component1() = x
    operator fun component2() = y
}
fun fromPolar(r: Double, phi: Double) = Point(r * cos(phi), r * sin(phi))

fun main(args: Array<String>) {
    val (x1, y1) = fromPolar(4.0, PI / 5)
    val (x2, y2) = fromPolar(3.0, 7 * PI / 10)
    val dx = x2 - x1
    val dy = y2 - y1
    val d = sqrt(dx * dx + dy * dy)
    println("distance = $d")  // distance = 5.0
}

Non-data classes must implement componentN().

Destructuring

fun printEntries(map: Map<String, String>) {
    for ((key, value) in map) {
        println("$key -> $value")
    }
}

fun main(args: Array<String>) {
    printEntries(mapOf(
            "040" to "Hamburg",
            "0221" to "Köln",
            "0611" to "Wiesbaden"
    ))
}
Output
040 -> Hamburg
0221 -> Köln
0611 -> Wiesbaden

Destructuring

fun printWithIndex(list: List<*>) {
    for ((index, element) in list.withIndex()) {
        println("$index: $element")
    }
}

fun main(args: Array<String>) {
    printWithIndex(listOf("Zero", "One", "Two"))
}
Output
0: Zero
1: One
2: Two

Destructuring

fun splitFilename(fullName: String) {
    val (name, extension) = fullName.split('.', limit = 2)
    println("name: $name, extension: $extension")
}

fun main(args: Array<String>) {
    splitFilename("scoop.kt")
}
Output
name: scoop, extension: kt

Variance, type projection

 

variance1

Variance, type projection

open class Animal(val name: String) {
    fun feed() = println("$name is no longer hungry")
}

class Cat(name: String, var hairColor: String? = null): Animal(name)

class Herd<T: Animal>(val list: List<T>) {
    fun get(name: String): T = list.first{it.name == name}
}

fun feedByName(name: String, herd: Herd<Animal>) {
    herd.get(name).feed()
}

fun main(args: Array<String>) {
    val animals = Herd(listOf(Animal("Unicorn"), Animal("Godzilla")))
    val cats = Herd(listOf(Cat("Nala"), Cat("Sammy", "orange")))

    feedByName("Godzilla", animals)
//    feedByName("Sammy", cats)        // compile error: Type mismatch
}

Variance, type projection

open class Animal(val name: String) {
    fun feed() = println("$name is no longer hungry")
}

class Cat(name: String, var hairColor: String? = null): Animal(name)

class Herd<T: Animal>(val list: List<T>) {
    fun get(name: String): T = list.first{it.name == name}
}

fun feedByName(name: String, herd: Herd<out Animal>) {     // use-site variance
    herd.get(name).feed()                                  // Java: <? extends Animal>
}

fun main(args: Array<String>) {
    val animals = Herd(listOf(Animal("Unicorn"), Animal("Godzilla")))
    val cats = Herd(listOf(Cat("Nala"), Cat("Sammy", "orange")))

    feedByName("Godzilla", animals)
    feedByName("Sammy", cats)
}

Variance, type projection

open class Animal(val name: String) {
    fun feed() = println("$name is no longer hungry")
}

class Cat(name: String, var hairColor: String? = null): Animal(name)

class Herd<out T: Animal>(val list: List<T>) {           // declaration-site variance
    fun get(name: String): T = list.first{it.name == name}
}

fun feedByName(name: String, herd: Herd<Animal>) {
    herd.get(name).feed()
}

fun main(args: Array<String>) {
    val animals = Herd(listOf(Animal("Unicorn"), Animal("Godzilla")))
    val cats = Herd(listOf(Cat("Nala"), Cat("Sammy", "orange")))

    feedByName("Godzilla", animals)
    feedByName("Sammy", cats)
}

Variance, type projection

open class Animal(val name: String) {
    fun feed() = println("$name is no longer hungry")
}

class Cat(name: String, var hairColor: String? = null): Animal(name)

class Herd<T: Animal>(val list: MutableList<T>) {
    fun accept(member: T) = list.add(member)
}

fun addGarfield(herd: Herd<Cat>) {
    herd.accept(Cat("Garfield", "orange"))
}

fun main(args: Array<String>) {
    val animals = Herd<Animal>(mutableListOf())
    val cats = Herd<Cat>(mutableListOf())

//    addGarfield(animals)    // compile error: Type mismatch
    addGarfield(cats)
}

Variance, type projection

open class Animal(val name: String) {
    fun feed() = println("$name is no longer hungry")
}

class Cat(name: String, var hairColor: String? = null): Animal(name)

class Herd<T: Animal>(val list: MutableList<T>) {
    fun accept(member: T) = list.add(member)
}

fun addGarfield(herd: Herd<in Cat>) {  // use-site variance (Java: <? super Cat>)
    herd.accept(Cat("Garfield", "orange"))
}

fun main(args: Array<String>) {
    val animals = Herd<Animal>(mutableListOf())
    val cats = Herd<Cat>(mutableListOf())

    addGarfield(animals)
    addGarfield(cats)
}

Variance, type projection

open class Animal(val name: String) {
    fun feed() = println("$name is no longer hungry")
}

class Cat(name: String, var hairColor: String? = null): Animal(name)

class Herd<in T: Animal>(val list: MutableList<in T>) { // declaration-site variance
    fun accept(member: T) = list.add(member)
}

fun addGarfield(herd: Herd<Cat>) {
    herd.accept(Cat("Garfield", "orange"))
}

fun main(args: Array<String>) {
    val animals = Herd<Animal>(mutableListOf())
    val cats = Herd<Cat>(mutableListOf())

    addGarfield(animals)
    addGarfield(cats)
}

Variance, type projection

open class Animal(val name: String) { fun feed() = println("$name is no longer hungry") }
class Cat(name: String, var hairColor: String? = null): Animal(name)

interface Producer<out T> { fun get(name: String): T }
interface Consumer<in T> { fun accept(member: T) }
class Herd<T: Animal>(val list: MutableList<T>): Producer<T>, Consumer<T> {
    override fun accept(member: T) { list.add(member) }
    override fun get(name: String): T = list.first{it.name == name}
}

fun feedByName(name: String, herd: Producer<Animal>) {
    herd.get(name).feed()
}
fun addGarfield(herd: Consumer<Cat>) {
    herd.accept(Cat("Garfield", "orange"))
}

fun main(args: Array<String>) {
    val animals = Herd<Animal>(mutableListOf(Animal("Unicorn"), Animal("Godzilla")))
    val cats = Herd<Cat>(mutableListOf(Cat("Nala"), Cat("Sammy", "orange")))

    feedByName("Godzilla", animals)
    feedByName("Sammy", cats)

    addGarfield(animals)
    addGarfield(cats)
}

Variance, type projection

variance2
Type Example Variance Restriction

Out-Projected

Producer<out Cat>

Covariance

Type parameter cannot be used as a function argument or setter

In-Projected

Consumer<in Cat>

Contravariance

If the type parameter is returned from a function or getter, its type will be Any? (or the upper-bound if you specified a type parameter constraint)

Star‑Projected

MutableList<*>

Every instance is a subtype

Both of the above restrictions apply

Lambdas

lambdas

 

val sum = { x: Int, y: Int -> x + y }
println(sum(1, 2))  // 3
  • you can store a lambda expression in a variable

Lambdas

class Person(val name: String, val age: Int)

fun main(args: Array<String>) {
    val people = listOf(Person("Alice", 29), Person("Bob", 31))
    println(people.joinToString(" ", transform = { p: Person -> p.name })) // Alice Bob
}
  • positional argument: separator
  • named argument: transform

Lambdas

class Person(val name: String, val age: Int)

fun main(args: Array<String>) {
    val people = listOf(Person("Alice", 29), Person("Bob", 31))
    println(people.joinToString(" ") { p: Person -> p.name })              // Alice Bob
}
  • the lambda expression can be moved out of the parentheses

Lambdas

class Person(val name: String, val age: Int)

fun main(args: Array<String>) {
    val people = listOf(Person("Alice", 29), Person("Bob", 31))
    println(people.joinToString(" ") { p -> p.name })                      // Alice Bob
}
  • the compiler can infer the parameter type

Lambdas

class Person(val name: String, val age: Int)

fun main(args: Array<String>) {
    val people = listOf(Person("Alice", 29), Person("Bob", 31))
    println(people.joinToString(" ") { it.name })                          // Alice Bob
}
  • you can use the default parameter name it

Lambdas

class Person(val name: String, val age: Int)

fun main(args: Array<String>) {
    val people = listOf(Person("Alice", 29), Person("Bob", 31))
    println(people.joinToString(" ", transform = Person::name))            // Alice Bob
}
  • you can replace the lambda expression with a member reference

Lambdas

fun printProblemCounts(responses: Collection<String>) {
    var clientErrors = 0
    var serverErrors = 0
    responses.forEach {
        if (it.startsWith("4")) {
            clientErrors++
        } else if (it.startsWith("5")) {
            serverErrors++
        }
    }
    println("$clientErrors client errors, $serverErrors server errors")
}

fun main(args: Array<String>) {
    val responses = listOf("200 OK", "418 I'm a teapot", "500 Internal Server Error")
    printProblemCounts(responses)    // 1 client errors, 1 server errors
}
  • you aren’t restricted to accessing final variables
  • you can modify variables from within a lambda

Lambdas

data class Person(val name: String, val age: Int)

fun main(args: Array<String>) {
    val people = listOf(Person("Alice", 31), Person("Bob", 29), Person("Carol", 31))

    val ageGroups = people.groupBy { it.age }
    println(ageGroups)
// {31=[Person(name=Alice, age=31), Person(name=Carol, age=31)], 29=[Person(name=Bob, age=29)]}

}

Lambdas

data class Person(val name: String, val age: Int)

fun main(args: Array<String>) {
    val people = listOf(Person("Alice", 31), Person("Bob", 29), Person("Carol", 31))

    val ageGroups = people.groupBy { it.age }
    println(ageGroups)
// {31=[Person(name=Alice, age=31), Person(name=Carol, age=31)], 29=[Person(name=Bob, age=29)]}

    println(ageGroups.mapValues{(_, list) -> list.map { it.name }})
// {31=[Alice, Carol], 29=[Bob]}
}

Lambdas

fun alphabet(): String {
    val result = StringBuilder()
    for (letter in 'A'..'Z') {
        result.append(letter)
    }
    result.append("\nNow I know the alphabet!")
    return result.toString()
}

fun main(args: Array<String>) {
    println(alphabet())
// ABCDEFGHIJKLMNOPQRSTUVWXYZ
// Now I know the alphabet!
}
  • how to avoid repeating the result name in each call?

Lambdas

fun alphabet(): String =
    with(StringBuilder()) {
        for (letter in 'A'..'Z') {
            append(letter)
        }
        append("\nNow I know the alphabet!")
        toString()
    }

fun main(args: Array<String>) {
    println(alphabet())
// ABCDEFGHIJKLMNOPQRSTUVWXYZ
// Now I know the alphabet!
}
  • with is a standard library function that uses lambdas with receivers (see also: let, run, also, apply)

Operators and conventions

  • Java: language features tied to specific classes (Iterable, AutoCloseable etc.)
  • Kotlin: features are tied to functions with specific names (conventions)
Overloadable arithmetic operators
Binary Operators   Unary operators
Expression Function name

a * b

times

a / b

div

a % b

mod

a + b

plus

a - b

minus

Expression Function name

+a

unaryPlus

-a

unaryMinus

!a

not

++a, a++

inc

--a, a--

dec

Operators and conventions: arithmetic operators

data class Point(val x: Int, val y: Int) {
    operator fun plus(other: Point): Point {
        return Point(x + other.x, y + other.y)
    }
}

fun main(args: Array<String>) {
    var p1 = Point(10, 20)
    val p2 = Point(30, 40)
    println(p1 + p2)    // Point(x=40, y=60)

    p1 += p2
    println(p1)         // Point(x=40, y=60)
}

Operators and conventions: arithmetic operators

data class Point(val x: Int, val y: Int)

operator fun Point.plus(other: Point): Point {
    return Point(x + other.x, y + other.y)
}

fun main(args: Array<String>) {
    var p1 = Point(10, 20)
    val p2 = Point(30, 40)
    println(p1 + p2)    // Point(x=40, y=60)

    p1 += p2
    println(p1)         // Point(x=40, y=60)
}

Operators and conventions: comparisons

class Person(val firstName: String, val lastName: String): Comparable<Person> {
    override fun equals(other: Any?): Boolean {
        if (other === this) return true
        if (other !is Person) return false
        return other.firstName == firstName && other.lastName == lastName
    }

    override fun compareTo(other: Person): Int =
        compareValuesBy(this, other, Person::lastName, Person::firstName)
}

fun main(args: Array<String>) {
    val p1 = Person("Alice", "Smith")
    val p2 = Person("Bob", "Johnson")
    println(p1 == p2)    // false
    println(p1 != p2)    // true
    println(p1 < p2)     // false
    println(p1 >= p2)    // true
}

Operators and conventions: indexed access

data class Point(val x: Int, val y: Int)

operator fun Point.get(index: Int) = when(index) {
    0 -> x
    1 -> y
    else -> throw IndexOutOfBoundsException("Invalid coordinate $index")
}







fun main(args: Array<String>) {
    val p = Point(10, 20)

    println(p[1])    // 20
}

Operators and conventions: indexed access

data class Point(var x: Int, var y: Int)

operator fun Point.get(index: Int) = when(index) {
    0 -> x
    1 -> y
    else -> throw IndexOutOfBoundsException("Invalid coordinate $index")
}

operator fun Point.set(index: Int, value: Int) = when(index) {
    0 -> x = value
    1 -> y = value
    else -> throw IndexOutOfBoundsException("Invalid coordinate $index")
}

fun main(args: Array<String>) {
    val p = Point(10, 20)
    p[1] = 42
    println(p[1])    // 42
}

Operators and conventions: the in operator

data class Point(val x: Int, val y: Int)

data class Rectangle(val upperLeft: Point, val lowerRight: Point)

operator fun Rectangle.contains(p: Point): Boolean {
    return p.x in upperLeft.x until lowerRight.x &&
            p.y in upperLeft.y until lowerRight.y
}

fun main(args: Array<String>) {
    val rect = Rectangle(Point(10, 20), Point(50, 50))
    println(Point(20, 30) in rect)   // true
    println(Point(5, 5) in rect)     // false
}
  • other convention functions: rangeTo and iterator

Coroutines

suspend fun requestImageUrls(query: String, count: Int = 20) = ...
suspend fun requestImageData(imageUrl: String) = ...

suspend fun createCollage(query: String, count: Int): BufferedImage {
    val urls = requestImageUrls(query, count)
    val images = mutableListOf<BufferedImage>()
    for (index in 0 until urls.size) {
        val image = requestImageData(urls[index])
        images += image
    }
    val newImage = combineImages(images)
    return newImage
}

Coroutines

suspend fun requestImageData(imageUrl: String) = suspendCoroutine<BufferedImage> { cont ->
    JerseyClient.url(imageUrl)
        .request(MediaType.APPLICATION_OCTET_STREAM)
        .async()
        .get(object : InvocationCallback<InputStream> {
            override fun completed(response: InputStream) {
                val image = ImageIO.read(response)
                cont.resume(image)
            }

            override fun failed(throwable: Throwable) {
                cont.resumeWithException(throwable)
            }
        })
}

Other things

  • inline functions
  • import aliases / typealias
  • annotations:

    • annotation targets: property, field, get, set, receiver, param, setparam, delegate, file
    • @Volatile, @Strictfp, @JvmName, @JvmStatic, @JvmOverloads, @JvmField

  • lateinit
  • tail recursion
  • reified type parameters
  • dokka / kdoc
  • converting Java code
  • using Java from Kotlin
  • using Kotlin from Java
  • test frameworks

build.gradle.kts for generating these slides

 ./gradlew asciidoctor
import com.github.jrubygradle.JRubyPrepare
import org.asciidoctor.gradle.AsciidoctorTask
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.gradle.api.Task
import org.gradle.jvm.tasks.Jar
import org.ysb33r.groovy.dsl.vfs.VFS

buildscript {
    dependencies {
        classpath("org.ysb33r.gradle:vfs-gradle-plugin:1.0")
        classpath("commons-httpclient:commons-httpclient:3.1")
    }
}

val version: String by project
val deckjsVersion: String by project
val asciidoctorBackendVersion: String by project
val downloadDir = File(buildDir,"download")
val templateDir = File(downloadDir,"templates")
val deckjsTmpDir = File(downloadDir,"deck.js.tmp")
val deckjsDir = File(downloadDir,"deck.js")
val deckjsVersionDir = File(downloadDir,"deck.js-master")

plugins {
    application
    kotlin("jvm") version "1.2.31"
    id("org.jetbrains.dokka") version "0.9.16"
    id("org.asciidoctor.convert") version "1.6.0"
    id("com.github.jruby-gradle.base") version "1.6.0"
    id("org.openjfx.javafxplugin") version "0.0.7"
}

repositories {
    jcenter()
    maven("https://dl.bintray.com/kotlin/exposed")
}

apply {
    plugin("application")
    plugin("org.jetbrains.dokka")
    plugin("com.github.jruby-gradle.base")
    plugin("org.ysb33r.vfs")
    plugin("org.asciidoctor.convert")
}
java {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
}
tasks.withType<KotlinCompile> {
    kotlinOptions.jvmTarget = "1.8"
}
group = "de.scoopsoftware"

javafx {
    modules = listOf("javafx.controls", "javafx.fxml")
}

dependencies {
    compile(kotlin("reflect"))
    compile(kotlin("stdlib"))
    compile("org.slf4j:slf4j-simple:1.7.25")
    compile("org.jetbrains.exposed:exposed:0.10.1")
    compile("com.h2database:h2:1.4.197")
    compile("io.javalin:javalin:1.4.1")
    compile("no.tornado:tornadofx:1.7.15")
    testCompile("io.kotlintest:kotlintest:2.0.7")
    gems("rubygems:haml:5.0.4")
    jrubyExec("org.jruby:jruby-complete:9.2.6.0")
}

tasks {
    register("download") {
        doLast {
            mkdir(downloadDir!!)
            with(VFS()) {
                cp(mapOf("recursive" to true, "overwrite" to true, "smash" to true),
                        "zip:https://github.com/asciidoctor/asciidoctor-deck.js/archive/${asciidoctorBackendVersion}.zip!asciidoctor-deck.js-${asciidoctorBackendVersion}/templates",
                        templateDir)
                cp(mapOf("recursive" to true, "overwrite" to true, "smash" to true),
                        "zip:https://github.com/imakewebthings/deck.js/archive/${deckjsVersion}.zip!deck.js-${deckjsVersion}",
                        deckjsDir)
            }
            deckjsVersionDir.renameTo(deckjsDir)
        }
    }
}
val downloadTask = tasks.get("download")
val jrubyPrepare = tasks.get("jrubyPrepare")

with(downloadTask) {
    description = "Download extra deckjs resources"
    outputs.dirs(templateDir, deckjsDir)
}

tasks.withType<Jar> {
    manifest {
        attributes(mapOf(
                "Implementation-Title" to "kotlinTalk",
                "Main-Class" to "de.scoopsoftware.kotlintalk.HelloWeb",
                "Implementation-Version" to version
        ))
    }
}

tasks.withType<AsciidoctorTask> {
    dependsOn(jrubyPrepare, downloadTask)

    sources(delegateClosureOf<PatternSet> {
        include("kotlin-slides.adoc")
    })

    resources(delegateClosureOf<CopySpec> {
        from (sourceDir) { include("images/**", "deck.js/**") }
        from (downloadDir) { include("deck.js/**") }
    })

    backends("html5")

    attributes.putAll(mapOf(
            "prjdir" to "${project.rootDir}",
            "sourcedir" to "${project.rootDir}/src/main",
            "imagesdir" to "./images"
    ))

    options(mapOf("template_dirs" to listOf(File(templateDir,"haml").absolutePath)))
}

application {
    mainClassName = "de.scoopsoftware.kotlintalk.HelloWeb"
}

tasks.register("bar") {
    group = "sample"
    doLast { println("Bar!") }
}

Resources

KotlinInAction