Do you want to learn Kotlin programming language from scratch? This is the 2nd episode of my simple course, focused on functions and functional programming.
{ 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)
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>)
} 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)
({ 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
() { 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.
(){ 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:
?: "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
"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
"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
= 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
== "Giuseppe Verdi" println( openDatabase("franco","secret") ?.findComposerBy(::exactMatchToGiuseppeVerdi) ?.findOperaByYear(1853) ?: "No results" ) Passing a function as a parameter using its name
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)
no result is found fun exactMatchToGiuseppeVerdi(composer: Composer): Boolean = composer.name == "Giuseppe Verdi" openDatabase("franco","secret") ?.findComposerBy(::exactMatchToGiuseppeVerdi) ?.findOperaByYear(1853) .run(::println)
== null) { println("No result") } else { println(this) } openDatabase("franco","secret") ?.findComposerBy(::exactMatchToGiuseppeVerdi) ?.findOperaByYear(1930) .run(::displayResult) Extension function on a nullable type
== 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)
(Opera::yearOfComposition) ?.map (Opera::name) ?: "No results" ) sortedBy doesn’t sort the original collection, but it returns a sorted copy of it: immutable data!
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
= this.composers.firstOrNull(predicate) A new object is created here openDatabase("franco", "secret") ?.findComposerBy { it.name.toLowerCase().startsWith("v") }
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
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
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/