5

In the following code,

class IncWrapper<T> (val wrapped: T, val base: Int) {
    fun incFunction(increment: Int, func: T.(Int) -> Int): Int {
        return increment + wrapped.func(base)
    }
}

class ClassWithIndecentlyLongName {
    fun square(x: Int) = x * x
}

fun main() {
    val wrapper = IncWrapper(ClassWithIndecentlyLongName(), 2)
    val computed = wrapper.incFunction(1, ClassWithIndecentlyLongName::square)
    println(computed)
}

we pass a reference to a method of the wrapped class ClassWithIndecentlyLongName. Since it is known at the call site that this class is expected as the receiver of the method, it seems awkward / redundant to pass the name of the class again. I'd expect something like ::square to work, but it doesn't. If such feature is missing, what might be the reasons?

(The question arose from an attempt to refactor some very wordy Java code converting lots of fields of one class to another.)

Inego
  • 1,039
  • 1
  • 12
  • 19
  • 1
    Related Java question: [Why class/object name must be explicitly specified for method references?](https://stackoverflow.com/q/30251867/2711488) – Holger Sep 23 '19 at 13:10

1 Answers1

5

Using just ::square would mean, it is part of your package or the file/class where it is called from. But that's not correct in that case.

If you have such long names you could switch from the function reference to the actual lambda instead, e.g.:

wrapper.incFunction(1) { square(it) }

If you have more parameter, then a typealias is probably more helpful instead, e.g.

typealias Functions = ClassWithIndecentlyLongName // choose a name that's more appropriate

// and calling it as follows:
wrapper.incFunction(1, Functions::square)

Alternatively import that class with an alias, e.g.:

import ClassWithIndecentlyLongName as ShortName

However, the better approach is probably to just discard the indecently long name and replace it with something more appropriate instead.

Finally, if you really want to just use ::square you can still do so, if you supply as many wrapper functions that you need, e.g.:

fun square(c : ClassWithIndecentlyLongName, i : Int) = c.square(i)

// calling it, now works as you wanted:
wrapper.incFunction(1, ::square)

Now: why it is that way? I can only guess. But it makes sense to me, that you need to specify exactly where that function can be found. I think it would rather complicate the code if you can never be sure which function is exactly behind the one specified.

Roland
  • 22,259
  • 4
  • 57
  • 84
  • In the body of an extension method, you don't even have to specify `this` to refer to a receiver when you access its properties or methods. In my opinion, this is the same "resolving from context" which could also be applied to resolving method references. – Inego Sep 13 '19 at 08:42
  • ok, from this point of view that might seem questionable. However, if you would allow `::square` being able to represent both, functions in the package or the one of the receiver, what should the compiler then do, if you specify a function within your package with a signature as shown in the last sample of my answer? Ambiguous usage error? But then you wouldn't be able to use that square-function anymore. You could clearly specify `CWILN::square` again but would not have the same possibility for the other function. So this seems a valid case why it shouldn't deviate it from the receiver type. – Roland Sep 13 '19 at 09:02
  • The same problem is applicable to resolving functions from call sites. If in the scope of a receiver a function being called is present both in the receiver and in the package, the receiver function is called, and to call the top-level package function, you have to fully qualify it (with the package name). See https://stackoverflow.com/a/45059254/1644036 for an example. – Inego Sep 13 '19 at 10:46
  • 1
    ok, except... how should that full qualification to the function reference look like? as far as I know there isn't something like that available for function references, is it? (i.e. `your.package::square` wouldn't work, as wouldn't `::your.package.square`). Putting it in another package, so that you can import it, is then just a workaround to that specific case and therefore no solution to it... – Roland Sep 13 '19 at 11:51