
This is a chapter from the book Functional Kotlin. You can find it on LeanPub or Amazon. It is also available as a course.
data class Complex(val real: Double, val imaginary: Double) { fun doubled(): Complex = Complex(this.real * 2, this.imaginary * 2) fun times(num: Int) = Complex(real * num, imaginary * num) } fun zeroComplex(): Complex = Complex(0.0, 0.0) fun makeComplex( real: Double = 0.0, imaginary: Double = 0.0 ) = Complex(real, imaginary) fun Complex.plus(other: Complex): Complex = Complex(real + other.real, imaginary + other.imaginary) fun Int.toComplex() = Complex(this.toDouble(), 0.0)
:: and a function name to reference a top-level function[^05_0]. Function references are part of the Kotlin reflection API and support introspection. If you include the kotlin-reflect dependency in your project, you can use a function reference to check if the referenced function has the open modifier, what annotation it has, etc.[^05_1]fun add(a: Int, b: Int) = a + b fun main() { val f = ::add // function reference println(f.isOpen) // false println(f.visibility) // PUBLIC // The above statements require `kotlin-reflect` // dependency }
fun add(a: Int, b: Int) = a + b fun main() { val f: (Int, Int) -> Int = ::add // an alternative to: // val f: (Int, Int) -> Int = { a, b -> add(a, b) } println(f(10, 20)) // 30 }
add is a function with two parameters of type Int, and result type Int, so its reference function type is (Int, Int) -> Int.zeroComplex and makeComplex should be?zeroComplex has no parameters, and its result type is Complex, so the function type of its function reference is () -> Complex. The function makeComplex has two parameters of type Double, and its result type is Complex, so the function type of its function reference is (Double, Double) -> Complex.fun zeroComplex(): Complex = Complex(0.0, 0.0) fun makeComplex( real: Double = 0.0, imaginary: Double = 0.0 ) = Complex(real, imaginary) data class Complex(val real: Double, val imaginary: Double) fun main() { val f1: () -> Complex = ::zeroComplex println(f1()) // Complex(real=0.0, imaginary=0.0) val f2: (Double, Double) -> Complex = ::makeComplex println(f2(1.0, 2.0)) // Complex(real=1.0, imaginary=2.0) }
makeComplex has default arguments for its parameters, it should also implement (Double) -> Complex and () -> Complex. Limited support for such behavior was introduced in Kotlin 1.4, but a reference must still be used as an argument.fun produceComplex1(producer: ()->Complex) {} produceComplex1(::makeComplex) fun produceComplex2(producer: (Double)->Complex) {} produceComplex2(::makeComplex)
:: and the method name. Every method needs a receiver, namely the object on which the function should be called. Function references expect it as the first parameter. Take a look at the example below.data class Number(val num: Int) { fun toFloat(): Float = num.toFloat() fun times(n: Int): Number = Number(num * n) } fun main() { val numberObject = Number(10) // member function reference val float: (Number) -> Float = Number::toFloat // `toFloat` has no parameters, but its function type // needs a receiver of type `Number` println(float(numberObject)) // 10.0 val multiply: (Number, Int) -> Number = Number::times println(multiply(numberObject, 4)) // Number(num = 40.0) // `times` has one parameter of type `Int`, but its // function type also needs a receiver of type `Number` }
toFloat function has no explicit parameters, but its function reference requires a receiver of type Number. The times function has only one explicit parameter of type Int, but it also requires another one for the receiver.sum and product from the introduction? We implemented them using lambda expressions, but we could also have used method references.fun sum(a: Int, b: Int) = (a..b).fold(0, Int::plus) fun product(a: Int, b: Int) = (a..b).fold(1, Int::times)
Complex::doubled and Complex::times?doubled has no explicit parameters, a receiver of type Complex, and the result type is Complex; therefore, the function type of its function reference is (Complex) -> Complex. times has an explicit parameter of type Int, a receiver of type Complex, and the result type is Complex; therefore, the function type of its function reference is (Complex, Int) -> Complex.data class Complex(val real: Double, val imaginary: Double) { fun doubled(): Complex = Complex(this.real * 2, this.imaginary * 2) fun times(num: Int) = Complex(real * num, imaginary * num) } fun main() { val c1 = Complex(1.0, 2.0) val f1: (Complex) -> Complex = Complex::doubled println(f1(c1)) // Complex(real=2.0, imaginary=4.0) val f2: (Complex, Int) -> Complex = Complex::times println(f2(c1, 4)) // Complex(real=4.0, imaginary=8.0) }
data class Number(val num: Int) fun Number.toFloat(): Float = num.toFloat() fun Number.times(n: Int): Number = Number(num * n) fun main() { val num = Number(10) // extension function reference val float: (Number) -> Float = Number::toFloat println(float(num)) // 10.0 val multiply: (Number, Int) -> Number = Number::times println(multiply(num, 4)) // Number(num = 40.0) }
Complex::plus and Int::toComplex from our basic functions?plus has a Complex parameter, a receiver of type Complex, and it returns Complex; therefore, the function type of its function reference is (Complex, Complex) -> Complex. The toComplex function has no parameters, a receiver of type Int, and it returns Complex; therefore, the function type of its function reference is (Int) -> Complex.data class Complex(val real: Double, val imaginary: Double) fun Complex.plus(other: Complex): Complex = Complex(real + other.real, imaginary + other.imaginary) fun Int.toComplex() = Complex(this.toDouble(), 0.0) fun main() { val c1 = Complex(1.0, 2.0) val c2 = Complex(4.0, 5.0) // extension function reference val f1: (Complex, Complex) -> Complex = Complex::plus println(f1(c1, c2)) // Complex(real=5.0, imaginary=7.0) val f2: (Complex, Int) -> Complex = Complex::times println(f2(c1, 4)) // Complex(real=4.0, imaginary=8.0) }
sum, which is an extension function on the type List<Int>, you need to use List<Int>::sum. If you want to reference isNullOrBlank, which is an extension property on the type String?, you should use String?::isNullOrBlank[^05_3].class TeamPoints(val points: List<Int>) { fun <T> calculatePoints(operation: (List<Int>) -> T): T = operation(points) } fun main() { val teamPoints = TeamPoints(listOf(1, 3, 5)) val sum = teamPoints .calculatePoints(List<Int>::sum) println(sum) // 9 val avg = teamPoints .calculatePoints(List<Int>::average) println(avg) // 3.0 val invalid = String?::isNullOrBlank println(invalid(null)) // true println(invalid(" ")) // true println(invalid("AAA")) // false }
unbox method, we need to use Box<String>::unbox, and the Box::unbox notation is not acceptable.class Box<T>(private val value: T) { fun unbox(): T = value } fun main() { val unbox = Box<String>::unbox val box = Box("AAA") println(unbox(box)) // AAA }
data class Number(val num: Int) { fun toFloat(): Float = num.toFloat() fun times(n: Int): Number = Number(num * n) } fun main() { val num = Number(10) // bounded function reference val getNumAsFloat: () -> Float = num::toFloat // There is no need for receiver type in function type, // because reference is already bound to an object println(getNumAsFloat()) // 10.0 val multiplyNum: (Int) -> Number = num::times println(multiplyNum(4)) // Number(num = 40.0) }
num::toFloat is () -> Float in the example above. We have previously learned that the function type of Number::toFloat is (Number) -> Float; therefore, in the regular method reference notation, the receiver type will be in the first position. In bounded function references, the receiver object is already provided in the reference, so there is no need to specify it additionally.doubled, times, plus, and toComplex? The answers can be found in the code below.data class Complex(val real: Double, val imaginary: Double) { fun doubled(): Complex = Complex(this.real * 2, this.imaginary * 2) fun times(num: Int) = Complex(real * num, imaginary * num) } fun Complex.plus(other: Complex): Complex = Complex(real + other.real, imaginary + other.imaginary) fun Int.toComplex() = Complex(this.toDouble(), 0.0) fun main() { val c1 = Complex(1.0, 2.0) val f1: () -> Complex = c1::doubled println(f1()) // Complex(real=2.0, imaginary=4.0) val f2: (Int) -> Complex = c1::times println(f2(17)) // Complex(real=17.0, imaginary=34.0) val f3: (Complex) -> Complex = c1::plus println(f3(Complex(12.0, 13.0))) // Complex(real=13.0, imaginary=15.0) val f4: () -> Complex = 42::toComplex println(f4()) // Complex(real=42.0, imaginary=0.0) }
object SuperUser { fun getId() = 0 } fun main() { val myId = SuperUser::getId println(myId()) // 0 val obj = object { fun cheer() { println("Hello") } } val f = obj::cheer f() // Hello }
class MainPresenter( private val view: MainView, private val repository: MarvelRepository ) : BasePresenter() { fun onViewCreated() { subscriptions += repository.getAllCharacters() .applySchedulers() .subscribeBy( onSuccess = this::show, onError = view::showError ) } fun show(items: List<MarvelCharacter>) { // ... view.show(items) } }
MainPresenter properties, but getAllCharacters should not know anything about this.this) can be used implicitly, so this::show can also be replaced with ::show.Complex class constructor, we need to use ::Complex. The constructor reference has the same parameters as the constructor it references, and its result type is the type of the class whose constructor it is.data class Complex(val real: Double, val imaginary: Double) fun main() { // constructor reference val produce: (Double, Double) -> Complex = ::Complex println(produce(1.0, 2.0)) // Complex(real=1.0, imaginary=2.0) }
class StudentId(val value: Int) class UserId(val value: Int) { constructor(studentId: StudentId) : this(studentId.value) } fun main() { val ints: List<Int> = listOf(1, 1, 2, 3, 5, 8) val studentIds: List<StudentId> = ints.map(::StudentId) val userIds: List<UserId> = studentIds.map(::UserId) }
::), then the method name.object Robot { fun moveForward() { /*...*/ } fun moveBackward() { /*...*/ } } fun main() { Robot.moveForward() Robot.moveBackward() val action1: () -> Unit = Robot::moveForward val action2: () -> Unit = Robot::moveBackward }
Companion by default.class Drone { fun setOff() {} fun land() {} companion object { fun makeDrone(): Drone = Drone() } } fun main() { val maker: () -> Drone = Drone.Companion::makeDrone }
fun foo(i: Int) = 1 fun foo(str: String) = "AAA" fun main() { println(foo(123)) // 1 println(foo("")) // AAA }

fun foo(i: Int) = 1 fun foo(str: String) = "AAA" fun main() { val fooInt: (Int) -> Int = ::foo println(fooInt(123)) // 1 val fooStr: (String) -> String = ::foo println(fooStr("")) // AAA }
class StudentId(val value: Int) data class UserId(val value: Int) { constructor(studentId: StudentId) : this(studentId.value) } fun main() { val intToUserId: (Int) -> UserId = ::UserId println(intToUserId(1)) // UserId(value=1) val studentId = StudentId(2) val studentIdToUserId: (StudentId) -> UserId = ::UserId println(studentIdToUserId(studentId)) // UserId(value=2) }
data class Complex(val real: Double, val imaginary: Double) fun main() { val c1 = Complex(1.0, 2.0) val c2 = Complex(3.0, 4.0) // property reference val getter: (Complex) -> Double = Complex::real println(getter(c1)) // 1.0 println(getter(c2)) // 3.0 // bounded property reference val c1ImgGetter: () -> Double = c1::imaginary println(c1ImgGetter()) // 2.0 }
var, you can reference the setter using the setter property from the property reference, but this requires kotlin-reflect; therefore, I recommend avoiding this approach because it might impact your code’s performance.[^05_1]: More about reflection in Advanced Kotlin, Reflection chapter.
[^05_2]: For this, the reference needs to be immediately typed as a function type.
[^05_3]: It is possible to reference this function by
String::isNullOrBlank, but such reference function type is (String) -> Boolean, makes it not accept null and effectively behave like String::isBlank.[^05_4]: More about object expressions and object declarations in Kotlin Essentials, Objects chapter.
[^05_5]: See Effective Kotlin, Item 32: Consider factory functions instead of secondary constructors.
[^05_6]: For details, see KEEP, link: kt.academy/l/keep-bound-ref
