Do you want to learn Kotlin programming language from scratch? This is the 2nd episode of a simple course, focused on function and functional programming
1. Kotlin from scratch 2
franco.lombardo@smeup.com - https://www.linkedin.com/in/francolombardo/
Functions
2.
3. Last episode: the power of lambdas
val elapsedTime = measureTimeMillis {
execute(compilationUnit.main.stmts)
}
public fun measureTimeMillis(block: () -> Unit): Long {
val start = System.currentTimeMillis()
block()
return System.currentTimeMillis() - start
}
4. Last episode: the power of lambdas
val elapsedTime = measureTimeMillis {
execute(compilationUnit.main.stmts)
}
public fun measureTimeMillis(block: () -> Unit): Long {
val start = System.currentTimeMillis()
block()
return System.currentTimeMillis() - start
}
This is a function (lambda expression)
5. What is a lambda?
A lambda is a literal of a function type
val x: Int = 5
5 is a literal of type Int
val f: (Int) -> String = { "The number is $it" }
This is a literal of type (Int) -> String
(Well, on JVM it’s an instance of Function1<Int, String>)
6. What is a lambda?
A lambda is a literal of a function type
val x: Int = 5
val f: (Int) -> String = { "The number is $it" }
it: implicit name of a single parameter
7. What is a lambda?
A lambda is a literal of a function type
val x: Int = 5
We can use a more explicit syntax
val f: (Int) -> String = { number: Int ->
"The number is $number"
}
8. What is a lambda?
A lambda is a literal of a function type
val x: Int = 5
Here is the evaluation of the function (y is a String)
val f = { number: Int ->
"The number is $number"
}
val y = f(x)
9. What is a lambda?
A lambda is a literal of a function type
val x: Int = 5
The function here is unevaluated (z is a function)
val f = { number: Int ->
"The number is $number"
}
val z = f
10. What is a closure?
A closure is a lambda that accesses its closure, i.e. the
variables declared in the outer scope
val x: Int = 5
val f = { "The number is $x" }
val y = f()
11. What is a closure?
Be careful!
Prints The number is 10
var x = 5
val f = { "The number is $x" }
x = 10
println ("${f()}")
12. The power of lambdas
val elapsedTime = measureTimeMillis {
execute(compilationUnit.main.stmts)
}
public fun measureTimeMillis(block: () -> Unit): Long {
val start = System.currentTimeMillis()
block()
return System.currentTimeMillis() - start
}
13. The power of lambdas
val elapsedTime = measureTimeMillis {
execute(compilationUnit.main.stmts)
}
public fun measureTimeMillis(block: () -> Unit): Long {
val start = System.currentTimeMillis()
block()
return System.currentTimeMillis() - start
}
Higher order function: a function that has a function parameter (or result)
14. Last episode: the power of lambdas
val elapsedTime = measureTimeMillis ({
execute(compilationUnit.main.stmts)
})
“Explicit” syntax
public fun measureTimeMillis(block: () -> Unit): Long {
val start = System.currentTimeMillis()
block()
return System.currentTimeMillis() - start
}
It’s the last parameter of this function
15. Last episode: the power of lambdas
val elapsedTime = measureTimeMillis () {
execute(compilationUnit.main.stmts)
}
public fun measureTimeMillis(block: () -> Unit): Long {
val start = System.currentTimeMillis()
block()
return System.currentTimeMillis() - start
}
It’s the last parameter of this function,
so we can pass it outside the parentheses.
16. Last episode: the power of lambdas
val elapsedTime = measureTimeMillis (){
execute(compilationUnit.main.stmts)
}
public fun measureTimeMillis(block: () -> Unit): Long {
val start = System.currentTimeMillis()
block()
return System.currentTimeMillis() - start
}
Since it’s the only argument,
the parentheses can be omitted entirely:
22. Named arguments
println(
openDatabase(user = "franco", "secret")
?.findComposerByName("Giuseppe Verdi")
?.findOperaByYear(1853)
?: "No results"
)
NO! It doesn’t compile. If you start with named arguments
you should go on from there
println(
openDatabase("as400", user = "franco", password = "secret")
?.findComposerByName("Giuseppe Verdi")
?.findOperaByYear(1853)
?: "No results"
)
OK
24. Composing null returning functions
println(
openDatabase("franco","secret")
?.findComposerByName("Giuseppe Verdi")
?.findOperaByYear(1853)
?: "No results"
)
fun Database.findComposerByName(name: String): Composer? =
this.composers.firstOrNull { it.name == name }
Extension function: we extend the Database class with a new method
25. Composing null returning functions
println(
openDatabase("franco","secret")
?.findComposerByName("Giuseppe Verdi")
?.findOperaByYear(1853)
?: "No results"
)
fun Database.findComposerByName(name: String): Composer? =
this.composers.firstOrNull { it.name == name }
Composer? → {Composer U null} (sum type)
findComposerByName is “total”
i.e. it maps each input value in one output value
27. Could we have a better composition?
println(
openDatabase("franco","secret")
?.findComposerByName("Giuseppe Verdi")
?.findOperaByYear(1853)
?: "No results"
)
28. “Good” functions compose easily
inline fun Database.findComposerBy(predicate: (Composer) -> Boolean) =
this.composers.firstOrNull(predicate)
fun similarName(name: String): (Composer) -> Boolean =
{ it.name.contains(name, ignoreCase = true) }
fun exactName(name: String): (Composer) -> Boolean =
{ it.name == name }
These functions have another function as a result
Higher order function
30. “Good” functions compose easily
println(
openDatabase("franco","secret")
?.findComposerBy(exactMatchToGiacomoPuccini)
?.findOperaByYear(1853)
?: "No results"
)
val exactMatchToGiacomoPuccini =
exactName("Giacomo Puccini")
Functions can be stored in variables
31. “Good” functions compose easily
fun exactMatchToGiuseppeVerdi(composer: Composer): Boolean =
composer.name == "Giuseppe Verdi"
println(
openDatabase("franco","secret")
?.findComposerBy(::exactMatchToGiuseppeVerdi)
?.findOperaByYear(1853)
?: "No results"
)
Passing a function as a parameter using its name
32. “Good” functions compose easily
You can use functions from the standard library too
fun exactMatchToGiuseppeVerdi(composer: Composer): Boolean =
composer.name == "Giuseppe Verdi"
openDatabase("franco","secret")
?.findComposerBy(::exactMatchToGiuseppeVerdi)
?.findOperaByYear(1853)
.run(::println)
33. “Good” functions compose easily
Extension function applicable to objects of any type:
it passes the object to the given function
fun exactMatchToGiuseppeVerdi(composer: Composer): Boolean =
composer.name == "Giuseppe Verdi"
openDatabase("franco","secret")
?.findComposerBy(::exactMatchToGiuseppeVerdi)
?.findOperaByYear(1853)
.run(::println)
34. “Good” functions compose easily
Not that good: prints null if no result is found
fun exactMatchToGiuseppeVerdi(composer: Composer): Boolean =
composer.name == "Giuseppe Verdi"
openDatabase("franco","secret")
?.findComposerBy(::exactMatchToGiuseppeVerdi)
?.findOperaByYear(1853)
.run(::println)
35. “Good” functions compose easily
fun Opera?.displayResult(): Unit =
if (this == null) {
println("No result")
} else {
println(this)
}
openDatabase("franco","secret")
?.findComposerBy(::exactMatchToGiuseppeVerdi)
?.findOperaByYear(1930)
.run(::displayResult)
36. “Good” functions compose easily
fun Opera?.displayResult(): Unit =
if (this == null) {
println("No result")
} else {
println(this)
}
openDatabase("franco","secret")
?.findComposerBy(::exactMatchToGiuseppeVerdi)
?.findOperaByYear(1930)
.run(::displayResult)
Extension function on a nullable type
37. “Good” functions compose easily
fun Opera?.displayResult(): Unit =
if (this == null) {
println("No result")
} else {
println(this)
}
openDatabase("franco","secret")
?.findComposerBy(::exactMatchToGiuseppeVerdi)
?.findOperaByYear(1930)
.run(::displayResult)
if statements are also expressions.
(The result here has type Unit)
38. Infix methods
infix fun Opera?.displayResultTo(printStream: PrintStream): Unit =
if (this == null) {
printStream.println("No result")
} else {
printStream.println(this)
}
openDatabase("franco","secret")
?.findComposerBy(exactMatchToGiacomoPuccini)
?.findOperaByYear(1901) displayResultTo System.err
39. “Good” functions compose easily
println(
openDatabase("franco","secret")
?.findComposerBy(exactMatchToGiacomoPuccini)
?.operas
?.maxBy (Opera::yearOfComposition)
?: "No results"
)
Functional API for collections (like map, sortBy, find…)
They get function parameters
A List
40. “Good” functions compose easily
println(
openDatabase("franco","secret")
?.findComposerBy(exactMatchToGiacomoPuccini)
?.operas
?.maxBy (Opera::yearOfComposition)
?: "No results"
)
Member reference: it’s like a lambda that has the object
as a parameter and invokes the method on it
A List
41. More functional APIs for collections
println(
openDatabase("franco","secret")
?.findComposerBy(exactMatchToGiacomoPuccini)
?.operas
?.sortedBy (Opera::yearOfComposition)
?.map (Opera::name)
?: "No results"
)
sortedBy doesn’t sort the original collection,
but it returns a sorted copy of it: immutable data!
42. Constructors are functions
class OperaHtmlDiv(val opera: Opera) {
override fun toString(): String =
"<div>${opera.name} ${opera.yearOfComposition}</div>"
}
println(
openDatabase("franco","secret")
?.findComposerBy(exactMatchToGiacomoPuccini)
?.operas
?.sortedBy (Opera::yearOfComposition)
?.map (::OperaHtmlDiv)
?: "No results"
)
We pass a constructor as a parameter
43. Do we really need all these checks?
println(
openDatabase("franco", "secret")
?.findComposerBy(exactMatchToGiacomoPuccini)
?.run {
operas
.sortedBy(Opera::yearOfComposition)
.map(::OperaHtmlDiv)
}
?: "No results"
)
Implicit this
44. The dark side of lambdas
fun Database.findComposerBy(predicate: (Composer) -> Boolean) =
this.composers.firstOrNull(predicate)
A new object is created here
openDatabase("franco", "secret")
?.findComposerBy {
it.name.toLowerCase().startsWith("v")
}
45. The dark side of lambdas
inline fun Database.findComposerBy(predicate: (Composer) -> Boolean) =
this.composers.firstOrNull(predicate)
Inlining the function the compiler doesn’t need a new object
openDatabase("franco", "secret")
?.findComposerBy {
it.name.toLowerCase().startsWith("v")
}
See https://www.baeldung.com/kotlin-inline-functions
• Use Inline with functions that have a lambda parameter
• With inline we can’t pass the lambda around,
so it can’t be the result of our higher order function
46. What is a “good” function?
• It is total, i.e. it maps each input value in one output value
(no exceptions)
• It works with immutable data types
(it doesn’t modify its parameters)
• It does not cause any observable side effects
• It returns the same result if given the same arguments
(it doesn’t depend on the environment)
• So it is referentially transparent, i.e. it can be replaced with its
corresponding value without changing the program's
behaviour
47. Good reads
Kotlin weekly
Great weekly newsletter: http://www.kotlinweekly.net/
Uberto Barbini on Medium
https://medium.com/@ramtop
Kotlin expertise blog by Simon Wirtz
https://kotlinexpertise.com/
Florina Muntenescu on Medium
https://medium.com/@florina.muntenescu
Kotlin blog by JetBrains
https://blog.jetbrains.com/kotlin/