Hi there!
The purpose of this thread is to discuss the degree to which we can/can’t write Pure Functions in Kotlin. For starters, this topic has been discussed before, and I understand that Kotlin does not enforce Pure Functions as a language feature. If I’m correct, this is mostly due to the fact that Kotlin and Java are “Pass by value of reference”.
At first, I thought this was a non issue because I’ve been hearing plenty of talk about “val” being a way to create “immutable data” (not necessarily from the Kotlin team, just on the interwebz). However, I’ve come to understand that val does not mean immutable, it means read only. So if I pass an argument to a function, even though it’s a val (which is always the case for function arguments), this only assures you that you can’t change the underlying object in the scope of the function itself.
So, here’s the point I’m interested in discussing. If we look at a definition of a Pure Function as far as the Haskell community is concerned (I’m willing to assume that they probably know better than the average imperative programmer about this topic), it requires:
-
Its return value is the same for the same arguments (no variation with local static variables, non-local variables, mutable reference arguments or input streams from I/O devices).
-
Its evaluation has no side effects (no mutation of local static variables, non-local variables, mutable reference arguments or I/O streams).
(source)
So, we could achieve these standards in the context of some “toy problem” where we don’t require any concurrency or data I/O, and there’s no possible way that a given mutable reference argument will ever change. This is typically the kind of code snippet I see when I read about people writing “Pure Functions” in Kotlin.
However, the moment any mutable reference to an argument exists outside of the scope of the function itself (which appears to be possible whenever working with var properties), the function is very not pure, at least by the above definition.
If the above reasoning is correct, then I get the following requirements for writing Pure Functions in Kotlin:
- If a given object can be verified to never have a mutable reference associated to it , in the context of the entire system, it can be regarded as truly immutable; therefore it could be used as an argument for a Pure Function (read: its immutable enough).
- One may not determine from a given function, whether or not it is pure, based on looking at it in isolation. This is because truly immutable arguments can be achieved, but are not enforced by the compiler.
Anyways, I’d like to affirm that I’m not here as a functional purist (I have an imperative upbringing myself), but I care very strongly about the fact that there might be a large number of people eagerly teaching beginners how to write what I’ll jokingly call “Contextually Pure Functions”, which appears to me as a major misunderstanding of the concept.
I’d be happy to hear any thoughts on this, including if my facts/reasoning are wrong at any point. Just trying to straighten some definitions out for myself, and possibly others by extension.
EDIT I’ve added a sample here to demonstrate my point that it’s possible to have a situation where unexpected state changes occur when concurrency is introduced. I’m admittedly trying to fail at concurrency and immutability, but my point is that it appears that you can’t determine if a function is pure in Kotlin, unless you look at it’s arguments in the context of the whole system.
import kotlinx.coroutines.experimental.delay
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.runBlocking
fun main(args: Array<String>) = runBlocking {
//Blin.a is initialized to 0
val b = Blin(0)
val c1 = launch {
printCurrentValue(b)
}
val c2 = launch {
b.a = 1
}
c1.join()
c2.join()
}
class Blin(var a: Int)
//Looking at this function alone, I have no idea of expecting b's state to change.
suspend fun printCurrentValue(b: Blin) {
doThing(b.a)
delay(1000L)
doThing(b.a)
}
//assume doThing is some long running operation
fun doThing(x: Int) {
println("I was called with $x")
}