Is "this" accessible in SAMs?


#1

In Android, I often see this as the recommended way to periodically call a function:

Handler handler = new Handler();
private Runnable runnableCode = new Runnable() {
    @Override
    public void run() {
      // Do something here
      // Repeat this the same runnable code block again another 2 seconds
      handler.postDelayed(runnableCode, 2000);
    }
};
handler.post(runnableCode);

I tried to convert this to Kotlin:

val handler = Handler()
val runnableCode = object : Runnable {
    override fun run() {
        // do something
        handler.postDelayed(runnableCode, 2000)
    }
}
handler.post(runnableCode)

This fails to compile with "Variable ‘runnableCode’ must be initialized. I don’t really understand this limitation, as the value is initialized. The only workaround I could think of was using handler.postDelayed(this, 2000), but this doesn’t seem to work for SAMs:

val handler = Handler()
val runnableCode = Runnable {
    // do something
    handler.postDelayed(this, 2000) // this does not exist
}
handler.post(runnableCode)

this does not exist within SAM function literals (if there’s a labeled this my IDE can’t find it), and I can’t find a way to access the Runnable instance itself. Is there a known workaround for this, or is it an intentional language limitation? I haven’t been able to find any documentation on the subject.

A simple try.kotlinlang.org code to demonstrate the issue:

fun main(args: Array<String>) {
    runnable.run()
    runnable2.run()
}
val runnable: Runnable = Runnable {    
    //println(this)  // does not compile
}
val runnable2: Runnable = object : Runnable {
    override fun run() {
        println(this)
    }
}

#2

I don’t know the original consideration behind this, but it makes sense to me: If you want to use the ‘SAM function literal’, which is more a functional programming style, ‘this’ pointer is kind of confusing.

So by forcing you to use the ‘object : Class’ anonymous class syntax, you will know it’s more a traditional class, where ‘this’ makes more sense.


Getting a this pointer to the enclosing lambda
#3

this in a lambda refers to the instance of the containing class, if any. A lambda is conceptually a function, not a class, so there is no such thing as a lambda instance to which this could refer.

The fact that a lambda can be converted into an instance of a SAM interface does not change this. Having this in a lambda mean different things depending on whether the lambda gets SAM-converted would be extremely confusing.


#4

I understand that a SAM lambda doesn’t really fit any of the rules documented at https://kotlinlang.org/docs/reference/this-expressions.html and would required a special case. I was just surprised that there was no reference to the instance being created from within itself.

It is also impossible to make them recursive or queue themselves with a Handler.


#5

By the way, while “this” does not work in a SAM, it does work in your original code:

val handler = Handler()
val runnableCode = object : Runnable {
    override fun run() {
        // do something
        handler.postDelayed(this, 2000)
    }
}
handler.post(runnableCode)

#6

Here’s what I did to work around the same problem:

fun runnable(body: Runnable.(Runnable)->Unit) = object: Runnable {
    override fun run() {
        this.body(this)
    }
}

Then you just change Runnable to runnable and everything works as you expect (and you can use this):

val handler = Handler()
val runnableCode = runnable {
    // do something
    handler.postDelayed(this, 2000) // perfectly OK now
}
handler.post(runnableCode)

#7

Since it’s an extension function you don’t need to pass this as a parameter. I normally use this:

inline fun runnable(crossinline body: Runnable.() -> Unit) = object : Runnable {
    override fun run() = this.body()
}

#8

Ah, yes, that is definitely cleaner than my way. For some reason, I kept thinking I had to pass this because of the SAM conversion, but of course, the whole point of this is it avoids the SAM conversion. How silly of me :slight_smile: Thanks for the correction!