Kotlin from scratch 2
franco.lombardo@smeup.com - https://www.linkedin.com/in/francolombardo/
Functions
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
}
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)
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>)
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
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"
}
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)
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
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()
What is a closure?
Be careful!
Prints The number is 10
var x = 5
val f = { "The number is $x" }
x = 10
println ("${f()}")
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
}
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)
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
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.
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:
Types of bodies
Types of bodies
See sample project: https://github.com/f-lombardo/kotlin-from-scratch
fun openDatabase(user: String, password: String): Database? =
if ("franco" == user && "secret" == password) {
sampleDatabase()
} else {
null
}
fun openDatabase(user: String, password: String): Database? {
if ("franco" == user && "secret" == password) {
return sampleDatabase()
}
return null
}
Block body
Expression body
Named arguments
println(
openDatabase("franco", "secret")
?.findComposerByName("Giuseppe Verdi")
?.findOperaByYear(1853)
?: "No results"
)
Which is the password?
Named arguments
println(
openDatabase(user = "franco", password = "secret")
?.findComposerByName("Giuseppe Verdi")
?.findOperaByYear(1853)
?: "No results"
)
Named arguments
println(
openDatabase(password = "secret", user = "franco")
?.findComposerByName("Giuseppe Verdi")
?.findOperaByYear(1853)
?: "No results"
)
Different order
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
Composition!!!
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
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
Composing null returning functions
println(
openDatabase("franco","secret")
?.findComposerByName("Giuseppe Verdi")
?.findOperaByYear(1853)
?: "No results"
)
“or else”
“and then”
Total functions (not throwing exceptions) compose easily
Could we have a better composition?
println(
openDatabase("franco","secret")
?.findComposerByName("Giuseppe Verdi")
?.findOperaByYear(1853)
?: "No results"
)
“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
“Good” functions compose easily
println(
openDatabase("franco","secret")
?.findComposerBy(similarName("verdi"))
?.findOperaByYear(1853)
?: "No results"
)
println(
openDatabase("franco","secret")
?.findComposerBy(exactName("Giuseppe Verdi"))
?.findOperaByYear(1853)
?: "No results"
)
“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
“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
“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)
“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)
“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)
“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)
“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
“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)
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
“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
“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
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!
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
Do we really need all these checks?
println(
openDatabase("franco", "secret")
?.findComposerBy(exactMatchToGiacomoPuccini)
?.run {
operas
.sortedBy(Opera::yearOfComposition)
.map(::OperaHtmlDiv)
}
?: "No results"
)
Implicit this
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")
}
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
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
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/

Kotlin from-scratch 2 - functions

  • 1.
    Kotlin from scratch2 franco.lombardo@smeup.com - https://www.linkedin.com/in/francolombardo/ Functions
  • 3.
    Last episode: thepower 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: thepower 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 alambda? 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 alambda? 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 alambda? 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 alambda? 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 alambda? 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 aclosure? 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 aclosure? Be careful! Prints The number is 10 var x = 5 val f = { "The number is $x" } x = 10 println ("${f()}")
  • 12.
    The power oflambdas 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 oflambdas 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: thepower 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: thepower 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: thepower 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:
  • 17.
  • 18.
    Types of bodies Seesample project: https://github.com/f-lombardo/kotlin-from-scratch fun openDatabase(user: String, password: String): Database? = if ("franco" == user && "secret" == password) { sampleDatabase() } else { null } fun openDatabase(user: String, password: String): Database? { if ("franco" == user && "secret" == password) { return sampleDatabase() } return null } Block body Expression body
  • 19.
    Named arguments println( openDatabase("franco", "secret") ?.findComposerByName("GiuseppeVerdi") ?.findOperaByYear(1853) ?: "No results" ) Which is the password?
  • 20.
    Named arguments println( openDatabase(user ="franco", password = "secret") ?.findComposerByName("Giuseppe Verdi") ?.findOperaByYear(1853) ?: "No results" )
  • 21.
    Named arguments println( openDatabase(password ="secret", user = "franco") ?.findComposerByName("Giuseppe Verdi") ?.findOperaByYear(1853) ?: "No results" ) Different order
  • 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
  • 23.
  • 24.
    Composing null returningfunctions 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 returningfunctions 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
  • 26.
    Composing null returningfunctions println( openDatabase("franco","secret") ?.findComposerByName("Giuseppe Verdi") ?.findOperaByYear(1853) ?: "No results" ) “or else” “and then” Total functions (not throwing exceptions) compose easily
  • 27.
    Could we havea better composition? println( openDatabase("franco","secret") ?.findComposerByName("Giuseppe Verdi") ?.findOperaByYear(1853) ?: "No results" )
  • 28.
    “Good” functions composeeasily 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
  • 29.
    “Good” functions composeeasily println( openDatabase("franco","secret") ?.findComposerBy(similarName("verdi")) ?.findOperaByYear(1853) ?: "No results" ) println( openDatabase("franco","secret") ?.findComposerBy(exactName("Giuseppe Verdi")) ?.findOperaByYear(1853) ?: "No results" )
  • 30.
    “Good” functions composeeasily println( openDatabase("franco","secret") ?.findComposerBy(exactMatchToGiacomoPuccini) ?.findOperaByYear(1853) ?: "No results" ) val exactMatchToGiacomoPuccini = exactName("Giacomo Puccini") Functions can be stored in variables
  • 31.
    “Good” functions composeeasily 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 composeeasily 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 composeeasily 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 composeeasily 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 composeeasily 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 composeeasily 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 composeeasily 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 funOpera?.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 composeeasily 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 composeeasily 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 APIsfor 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 classOperaHtmlDiv(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 reallyneed all these checks? println( openDatabase("franco", "secret") ?.findComposerBy(exactMatchToGiacomoPuccini) ?.run { operas .sortedBy(Opera::yearOfComposition) .map(::OperaHtmlDiv) } ?: "No results" ) Implicit this
  • 44.
    The dark sideof 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 sideof 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 Greatweekly 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/