Search code examples
kotlinlambdaextension-methodsktor

Pass lambda as extension method


I'm currently learning kotlin and have came into the following scenario. In Ktor server there's a method with the following signature:

fun Route.webSocket(protocol: String? = null, handler: suspend DefaultWebSocketServerSession.() -> Unit) {
    webSocketRaw(protocol) {
        proceedWebSocket(handler)
    }
}

where I'm supposed to interact with it somehow like this:

embeddedServer(Netty, 8080) {
        install(Routing) {
            webSocket("/ws") {
                // Handle websocket connection here
            }
        }
}

Meaning websocket accepts labda that's an extension method of DefaultWebSocketServerSession and has it's context. I would like to convert this lambda into a handler so I can pass it from somewhere else, I imagine it should look something like that:

embeddedServer(Netty, 8080) {
        install(Routing) {
            webSocket("/ws", myHandler::handle)
        }
}

//...
fun suspend handle(context: DefaultWebSocketServerSession): Unit {
    // Handle websocket connection here
}

So, my question is how do I convert suspend DefaultWebSocketServerSession.() -> Unit to(DefaultWebSocketServerSession) -> Unit, or how do I Implement a handler with suspend DefaultWebSocketServerSession.() -> Unit signature so I can pass it from the outside?

PS

I know I could do this

embeddedServer(Netty, 8080) {
        install(Routing) {
            webSocket("/ws") {
                myHandler.handle(this)
            }
        }
}

But that doesn't feel elegant


Solution

  • There is no need to convert anything yourself. Kotlin converts between Method References, Function Literals and Function Literals with Receiver. Look at this example:

    class A
    
    class AHandler {
      fun handle(a: A) {
        println("AHandler $a")
      }
    }
    
    fun useLambdaWithReceiver(lambda: A.()->Unit) {
      val a = A()
      lambda(a)
    }
    
    fun useNormalLambda(lambda: (A)->Unit) {
      useLambdaWithReceiver(lambda)
    }
    
    fun main() {
      val handler = AHandler()
      useLambdaWithReceiver {
        println("useLambdaWithReceiver $this")
      }
      useNormalLambda {
        println("useNormalLambda $it")
      }
      useLambdaWithReceiver(handler::handle)
      useNormalLambda(handler::handle)
    }
    

    Output:

    useLambdaWithReceiver A@174d20a
    useNormalLambda A@3c756e4d
    AHandler A@2ef5e5e3
    AHandler A@6d00a15d
    

    Everything compiled and converted automatically. So, you can just pass your handler method and it should be fine, unless there is a bug with suspend modifier I am not aware of.