4

I'm playing with Kotlin's extension functions. I'd like to create an extension function for boolean-returning function with receivers which returns the complement function.

My goal is to be able to write:

val isEven: Int.() -> Boolean = { this % 2 == 0 }
val isOdd: Int.() -> Boolean = isEven.complement()

I do realize there are better, clearer ways for doing what I'm doing, I want to better understand the language itself here, so please don't tell me to write isOdd as { !isEven() } :)

I want something like:

fun <I> (I.() -> Boolean).complement(): I.() -> Boolean = TODO()

Now,

fun <I> (I.() -> Boolean).complement(): I.() -> Boolean = this

compiles correctly, so the syntax definitely makes sense here. The problem is that this is of type I.() -> Boolean, and I'd need to access the "inner receiver" of my function with receiver, something like this@this, to write something like:

fun <I> (I.() -> Boolean).complement(): I.() -> Boolean = { !(this@this).this() }

where this@this would be of type I. Is there any way of achieving this effect?

Also, I noticed that I do not know how to invoke a function with receiver, I tried to:

fun <I> (I.() -> Boolean).complement(innerthis: I): I.() -> Boolean = { !this(innerthis) }

I get an error: expression 'this' of type 'I' cannot be invoked as a function. The function 'invoke()' is not found.

This sounds wrong to me! this should have type I.() -> Boolean, not I! I can't wrap my head around this error message.

I thought that maybe I'm just using a wrong syntax, so I changed to:

fun <I> (I.() -> Boolean).complement(innerthis: I): I.() -> Boolean = { !innerthis.this() }

But I get the same error. This is very confusing to me, as I'd expected innerthis to by of type I, and this of type I.() -> Boolean. My expectation seems to be confirmed by the implementation with = this to be compiling flawlessly.

Can someone explain to me the error the compiler is raising?

Thanks!

Danilo Pianini
  • 966
  • 8
  • 19

1 Answers1

3

You can disambiguate the outer this by the function name:

fun <I> (I.() -> Boolean).complement(): I.() -> Boolean = { !this@complement(this) }

Here this@complement is the receiver of complement function, and plain this is the receiver of the lambda function literal. For simplicity, this@complement is called like a function with one argument, however it is possible to call it as an extension function as well using a bit more tricky syntax:

fun <I>  (I.() -> Boolean).complement(): I.() -> Boolean = { !this.(this@complement)() }
Ilya
  • 21,871
  • 8
  • 73
  • 92
  • 1
    FYI: If you type `this` into IntelliJ, it actually suggests both `this : I` and `this@complement : I.() -> Boolean`; it's a very smart IDE! Definitely helps getting your head around it. – charles-allen Nov 07 '19 at 10:49
  • Thanks! That's it. The root of my mistake was into considering `this` as being bound to the outermost receiver, while in fact it yields the *innermost* receiver, hence allowing for disambiguation via `@` syntax. Also, I didn't know the second syntax you proposed. Thanks again. – Danilo Pianini Nov 07 '19 at 10:50
  • 1
    @AjahnCharles yes, but I usually prefer to exercise language features within a REPL, in order to force myself into reasoning on types without the help I usually get from an IDE -- which I agree, would have helped a lot in this case. – Danilo Pianini Nov 07 '19 at 10:53