2

I wonder if a data class with one of the properties being a function, such as:

data class Holder(val x: Data, val f: () -> Unit)

can work at all, since the following test fails.

val a = {}
val b = {}
Assert.assertEquals(a, b)

Update: Use case for this could be to have a

data class ButtonDescriptor(val text: String, val onClick: () -> Unit)

and then flow it to UI whilst doing distinctUntilChanged()

clearpath
  • 916
  • 8
  • 24
  • 1
    "I wonder if ... can work at all" Well, why not try it and find out? – Sweeper Jun 11 '21 at 07:53
  • 1
    Well, it works . It checks if this is the same function, so e.g. `Holder(::foo) == Holder(::foo)` returns `true`. If you mean to compare functions/lambdas for similar code inside them, then no, it doesn't seem possible. – broot Jun 11 '21 at 08:35
  • @broot yes, somehow to compare two lambdas for structural equality including code inside, captured variables, etc. – clearpath Jun 11 '21 at 10:05
  • This is not possible, but is there a reason why you want to do this with lambdas instead of OOP here? If you define a parent class with an abstract method, and a child classes with the data and the implementation, the equality check could check that the 2 instances are of the same subclass in addition to comparing the data. The captures etc will become explicit, but that's not necessarily bad :) – Joffrey Jun 11 '21 at 12:01

3 Answers3

1

I don't think this is possible, I'm afraid.

You can of course check reference equality (===, or == in this case because functions don't generally override equals()).  That would give you a definite answer where you have references to the same function instance.  But that doesn't check structural equality, and so reports the two lambdas in the question as different.

You can check whether the two functions are instances of the same class by checking their .javaClass property.  If the same, that would imply that they do the same processing — though I think they could still have different variables/captures.  However, if different, that wouldn't tell you anything.  Even the simple examples in the question are different classes…

And of course, you can't check them as ‘black boxes’ — it's not feasible to try every possible input and check their outputs.  (Even assuming they were pure functions with no side effects, which in general isn't true!)

You might be able to get their bytecode from a classloader, and compare that, but I really wouldn't recommend it — it'd be a lot of unnecessary work, you'd have to allow for the difference in class names etc., it would probably have a lot of false negatives, and again I think it could return the same code for two functions which behaved differently due to different parameters/captures.

So no, I don't think this is possible in JVM languages.

What are you trying to achieve with this, and could there be another way?  (For example, if these functions are under your control, can you arrange for reference equality to do what you need?  Or could you use function objects with an extra property giving an ID or something else you could compare?)

gidds
  • 16,558
  • 2
  • 19
  • 26
  • Use case for this could be to have a `data class ButtonDescriptor(val text: String, val onClick: () -> Unit)` and then flow it to UI whilst doing `distinctUntilChanged()` – clearpath Jun 15 '21 at 08:56
1

When you create your data class, if you pass the function by reference it will work with DiffUtils and distinctUntilChanged(). Function references do not break the isEquals() method of data classes in the same way that a lambda does.

For example, you create a function for your onClick:

private fun onClick() { // handle click }

and create your data class like

BottomDescriptor("some text", ::onClick)
Jason Toms
  • 850
  • 2
  • 10
  • 23
0

Something you can do is create a data class that extends the function definition and then kotlin will be able to determine the equality of the data classes.

import kotlinx.*

fun main() {
    val functionHolderA = FunctionHolder(Function("Test"))
    val functionHolderB = FunctionHolder(Function("Test"))
    println(functionHolderA == functionHolderB)
}

data class FunctionHolder(val function: () -> String)

data class Function(val name: String): () -> String {
    override fun invoke() = name
}

Kotlin Playground