https://github.com/jeffgbutler/practical-functional-kotlin
https://jeffgbutler.github.io/practical-functional-kotlin/
"Most people talk about Java the language, and this may sound odd coming from me, but I could hardly care less. At the core of the Java ecosystem is the JVM."
James Gosling, Creator of the Java Programming Language(2011, TheServerSide.com)
Java is a good language...there are some issues...you can make things as God awful as you want... it does allow people to create incredible messes (Gosling)
Kotlin is one of the most Java-ish things ever to happen to Java
What do I mean?
¯\_(ツ)_/¯
I dream that late one night, some JetBrains folks decide they have finally had enough of Java's lethargy. They figure they can build something better.
In some ways, Kotlin is all about syntax sugar
Some of it is a little like Lombok - but not really
All high level languages are just syntax sugar for assembler anyway - so we'll just get over it :)
Now we'll look at some code...
val foo = "Fred" // String type inferred, immutable, non-nullable
var bar = "Barney" // String type inferred, mutable, non-nullable
var baz: String? = "Betty" // String type, mutable, nullable
Variables do not need to be in a class!
fun helloWorld1(s: String): Unit { // Unit is like void, and not necessary in this case
println("Hello World!")
}
fun helloWorld2(s: String) = println("Hello World!") // one line functions don't need braces
fun add1(a: Int, b: Int): Int {
return a + b
}
fun add2(a: Int, b: Int) = a + b // return type of Int is inferred
Functions do not need to be in a class!
class MyCalculator1 (val firstName: String, val lastName: String) {
fun sayHello() = "Hello!"
}
Notes:
interface Friendly {
fun sayHello(): String
fun isFriendly() = true
}
Notes:
class MyCalculator2 (val firstName: String, val lastName: String) : Friendly {
override fun sayHello(): String {
return "Hi! My name is " + fullName
}
val fullName: String
get() = firstName + " " + lastName // a "getter" in Kotlin
}
Notes:
data class Person(val firstName : String, val lastName : String)
This class ...
Java
public String renderGreeting(Boolean isFriendly) {
if (isFriendly) {
return "Hello!";
} else {
return "Go Away!";
}
}
Kotlin
fun renderGreeting1(friendly: Boolean): String {
return if (friendly) {
"Hello!"
} else {
"Go Away!"
}
}
fun renderGreeting2(friendly: Boolean) =
if (friendly) "Hello!" else "Go Away!"
Notes
Java
public String calculateLastName(String firstName) {
String rc;
switch(firstName) {
case "Fred":
case "Wilma":
rc = "Flintstone";
break;
case "Barney":
case "Betty":
rc = "Rubble";
break;
default:
rc = "Unknown";
break;
}
return rc;
}
Kotlin
fun calculateLastName(firstName: String): String {
return when(firstName) {
"Fred", "Wilma" -> "Flintstone"
"Barney", "Betty" -> "Rubble"
else -> { // can be a code block
return "Unknown"
}
}
}
fun renderLifeStage1(age: Int): String {
return when(age) {
in 0..12 -> "Child"
in 13..19 -> "Teenager"
else -> "Adult"
}
}
fun renderLifeStage2(age: Int): String {
return when {
age < 13 -> "Child"
age in 13..19 -> "Teenager"
else -> "Adult"
}
}
Note: Any boolean expression is allowed
Sealed classes create a fixed hierarchy
// all sealed classes must be in the same file...
sealed class Animal(val name: String)
class Dog(name: String, val akcNumber: Int) : Animal(name)
class Cat(name: String, val cfaNumber: Int) : Animal(name)
fun speak(animal: Animal): String {
return when (animal) {
is Dog -> "Bark"
is Cat -> "Meow"
// no otherwise/else!
}
}
var myDog = Dog("Bob Barker", 22) // no new keyword!
var talk = speak(myDog) // Bark
Avoids an ugly instanceof check
Same outcome as visitor, but far simpler to understand
data class Attribute (val name: String, val value: String)
// declare a "render" extension function
// extension functions can be in different packages,
// different JARs, different projects
fun Attribute.render(): String {
// extension methods have access non-private
// attributes and methods
// this is a Kotlin string template
return """$name="$value" """
}
val att = Attribute("foo", "bar")
val renderedString = att.render() // foo="bar"
sealed class Animal(val name: String)
class Dog(name: String, val akcNumber: Int) : Animal(name)
class Cat(name: String, val cfaNumber: Int) : Animal(name)
// add an extension method to Animal
fun Animal.speak(): String {
return when (this) {
// string template preview!
is Dog -> "Bark! My name is $name and my number is $akcNumber"
is Cat -> "Meow! My name is $name and my number is $cfaNumber"
}
}
var myDog = Dog("Bob Barker", 22)
var talk = myDog.speak() // Bark! My name is Bob Barker and my number is 22
Every Java reference can be null. This requires a lot of null checking or Optional wrapping.
public class Person {
private Address mailingAddress;
public Address getMailingAddress() {
return mailingAddress;
}
}
public class Address {
private String secondAddresLine;
public String getSecondAddressLine() {
return secondAddresLine;
}
}
public String getSecondMailingAddress(Person person) {
if (person == null) {
return "";
}
if (person.getMailingAddress() == null) {
return "";
}
if (person.getMailingAddress().getSecondAddressLine()
== null) {
return "";
}
return person.getMailingAddress().getSecondAddressLine()
.trim();
}
Kotlin references can only be null if specifically declared to allow null
data class Person (val mailingAddress: Address?)
data class Address (val secondAddressLine: String?)
String getSecondMailingAddress(person: Person?) =
person?.mailingAddress?.secondAddressLine?.trim() ?: ""
val bob = Person(homeAddress = Address("Main Street"), name = "Bob")
val bobString1 = "My name is ${bob.name}, my home address is ${bob.homeAddress.line1}"
// triple quoted string - preserves formatting
val bobString2 = """|My name is "${bob.name}"
| My home address is ${bob.homeAddress.line1}"""
.trimMargin("|") // "|" is default and can be omitted
class BankAccount (var balance: Double = 0.0){
operator fun plusAssign(amount: Double) { // overloads +=
balance += amount
}
}
val myAccount = BankAccount()
myAccount.plusAssign(22.0) // non overloaded call
myAccount += 22.0 // over loaded call
Many operators can be overloaded - some in very clever ways. For example, if a class
implements Comparable
, you get <, >, <=, >= for free.
New operators cannot be defined...except infix...
class BankAccount (var balance: Double = 0.0){
infix fun deposit(amount: Double) {
balance += amount
}
infix fun `!&%`(amount: Double) {
balance -= amount
}
}
val myAccount = BankAccount()
myAccount.deposit(22.0) // non infix call
myAccount deposit 22.0 // infix call
myAccount `!&%` 33.0
@Test
fun `simple lambda`() {
val names = listOf("Fred", "Wilma", "Barney", "Betty")
var str = names.filter ({ s -> s.startsWith("B") })
.joinToString(",")
assertThat(str).isEqualTo("Barney,Betty")
}
@Test
fun `lambda with it`() {
val names = listOf("Fred", "Wilma", "Barney", "Betty")
var str = names.filter({ it.startsWith("B") })
.joinToString(",")
assertThat(str).isEqualTo("Barney,Betty")
}
Note the "it" variable name
@Test
fun `lambda pipeline`() {
val names = listOf("Fred", "Wilma", "Barney", "Betty")
var str = names.filter({ it.startsWith("B") })
.map({ it.toUpperCase() })
.joinToString(",")
assertThat(str).isEqualTo("BARNEY,BETTY")
}
@Test
fun `lambda combination functions`() {
val names = listOf("Fred", "Wilma", "Barney", "Betty")
var str = names.filter({ it.startsWith("B") })
.joinToString(",", transform = { it.toUpperCase() })
assertThat(str).isEqualTo("BARNEY,BETTY")
}
@Test
fun `lambda outside parenthesis`() {
val names = listOf("Fred", "Wilma", "Barney", "Betty")
var str = names.filter { it.startsWith("B") }
.map { it.toUpperCase() }
.joinToString(",")
assertThat(str).isEqualTo("BARNEY,BETTY")
}
@Test
fun `method references`() {
val names = listOf("Fred", "Wilma", "Barney", "Betty")
var str = names.filter { it.startsWith("B") }
.map(String::toUpperCase)
.joinToString(",")
assertThat(str).isEqualTo("BARNEY,BETTY")
}
@Test
fun `function variables and local functions`() {
val upperCase = { s: String -> s.toUpperCase() } // function variable
fun String.startsWithB() = startsWith("B") // local extension function
val names = listOf("Fred", "Wilma", "Barney", "Betty")
var str = names.filter { it.startsWithB() }
.map { upperCase(it) }
.joinToString(",")
assertThat(str).isEqualTo("BARNEY,BETTY")
}
@Test
fun `higher order functions`() {
val names = listOf("Fred", "Wilma", "Barney", "Betty")
var str = names.filter { it.startsWith("B") }
.map(upperCaseIt())
.joinToString(",")
assertThat(str).isEqualTo("BARNEY,BETTY")
}
// return a function that takes a String and returns a String
// "(String) -> String" is the return type
private fun upperCaseIt(): (String) -> String = { it.toUpperCase() }
private fun upperCaseIt2() = { s: String -> s.toUpperCase() } // using inferred type
The "crown jewel" of Kotlin. Uses "clean syntax" features to create a natural looking DSL
fun result(args: Array<String>) =
html {
head {
title {+"XML encoding with Kotlin"}
}
body {
h1 {+"XML encoding with Kotlin"}
p {+"this format can be used as an alternative markup to XML"}
a(href = "http://kotlinlang.org") {+"Kotlin"}
p {
+"This is some"
b {+"mixed"}
+"text. For more see the"
a(href = "http://kotlinlang.org") {+"Kotlin"}
+"project"
}
p {+"some text"}
p {
for (arg in args)
+arg
}
}
}