리시버가 있는 함수 리터럴 (Function Literals with Receiver)


 코틀린은 지정된 receiver 객체로 함수 리터럴을 호출 할 수 있도록 합니다. 함수 리터럴의 body내에서, 어떤 추가적인 한정자 없이 해당 receiver 객체 메서드를 호출할 수 있습니다. 마치 함수의 body 내부에 있는 receiver 객체의 멤버에 접근 가능한 확장 함수처럼요.


 그러한 함수 리터럴의 타입은 다음과 같이 receiver를 가지는 함수 타입입니다.


 sum : Int.(other: Int) -> Int


 와닿지 않으니 예제를 한 번 실행해보겠습니다.


fun main(args: Array<String>) {

    val sum1 : Int.(num: Int) -> Int = {this+it}

    val sum2 : Int.(Int) -> Int = {this+it}

    println(1.sum1(2))

    println(1.sum2(2))

}

3
3


 람다식은 연습겸 조금 다르게 하나 더 표기해봤습니다.


 익명 함수 문법은 직접적으로 함수 리터럴의 receiver 타입을 지정할 수 있도록 합니다. 이것은 만약에 receiver를 가지는 함수 타입의 변수를 선언하고 추후에 그 변수를 사용할 때 유용합니다.


fun main(args: Array<String>) {

    val sum = fun Int.(num: Int): Int = this + num

    println(1.sum(2))

}


 람다 표현식은 receiver 타입이 문맥상 유추될 수 있을 때 receiver를 가진 함수 리터럴로 사용될 수 있습니다.


class HTML {

    fun body() { ... }

}


fun html(init: HTML.() -> Unit): HTML {

    val html = HTML()  // create the receiver object

    html.init()        // pass the receiver object to the lambda

    return html

}



html {       // lambda with receiver begins here

    body()   // calling a method on the receiver object

}


 잘 이해가 안되네요. 다음과 같이 보면 좀 도움이 될 것 같습니다.


 fun main(args: Array<String>) {

    val test : (HTML.() -> Unit) = {

        println("test")

    }

    html(test)

}


class HTML {

    fun body() {

        println("HTML")

    }

}


fun html(aaa: HTML.() -> Unit): HTML {

    val html = HTML()  // receiver 객체 생성

    html.aaa()        // receiver 객체를 람다로 전달

    return html

}



//html {       // receiver가 있는 람다의 시작점

//    body()   // receiver 객체의 메소드를 호출

//}

test


익명 함수 (Anonymous Function)


 앞서 살펴본 람다식 문법에서는 함수의 리턴 타입을 지정하는 방법이 누락되어 있습니다. 대부분 리턴 타입을 자동으로 유추할 수 있기에 불필요하기 때문입니다. 하지만 리턴 타입을 명시적으로 지정해야 할 필요가 있으면 익명 함수를 사용할 수 있습니다.


 익명 함수는 함수명이 없다는 것을 빼면 보통 함수 선언과 거의 똑같은 모습을 하고 있습니다. body도 다음과 같이 표현식이나 블록으로 선언할 수 있습니다.


표현식

fun(x: Int, y: Int): Int = x + y


블록

fun(x: Int, y: Int): Int {

        return x + y

}


 파라미터와 리턴 타입은 보통 함수와 동일한 방법으로 지정합니다. 단, 파라미터 타입을 문맥상 유추할 수 있으면 생략할 수 있습니다.


 람다식에서 qualified return syntax를 이용하는 것이 다소 번거롭기 때문에 람다식 대신에 익명 함수를 사용할 수 있습니다.


fun main(args: Array<String>) {

    val isPositive = fun(num: Int): Boolean {

        val isPositive = num > 0

        return isPositive

    }

    println("isPositive(10) = ${isPositive(10)}")

}

 isPositive(10) = true


 또한 위에서 말했듯이 리턴 타입을 명시적으로 지정해야 할 필요가 있으면 다음과 같이 익명 함수를 사용할 수 있습니다.


fun main(args: Array<String>) {

    val returnAny = {

        "retyrnAny" as Any

    }


    val returnAnyTest = fun(): Any = "returnAnyTest"


    println("returnAny = ${returnAny()}")

    println("returnAny2 = ${returnAnyTest()}")

}

returnAny = retyrnAny

 returnAny2 = returnAnyTest


 Any로 리턴 타입을 명시적으로 지정했지만 println에서 toString()으로 변환되어 출력된 것입니다.

람다 표현식 문법 (Lambda Expression Syntax)



 함수 타입의 리터럴인 람다식의 완전한 문법적 형태는 다음과 같습니다.


val sum = { x: Int, y: Int -> x + y}


 람다식은 항상 중괄호{ }로 묶여 있습니다. 완전한 문법적 형태(full syntactic form)에서는 중괄호 내에 파라미터를 선언하고 생략 가능한 type annotation을 가집니다. body는 -> 뒤에 옵니다. 만약에 유추된 람다의 리턴 타입이 Unit이 아닌 경우에는 람다 body의 내에 마지막 표현식이 리턴 값으로 처리됩니다.


 즉, 위의 예에서 모든 생략 가능한 annotation을 생략하지 않은 형태는 다음과 같습니다.


val sum: (Int, Int) -> Int = { x, y -> x + y }


 type annotation(Int, Int)이며 리턴 타입은 Int입니다. 만약 리턴 타입이 생략되었다면 -> 뒤에 오는 body인 x + y가 리턴 값으로 처리되겠죠.


 결국 함수이기 때문에 함수명(파라미터)으로 사용할 수 있습니다.


fun main(args: Array<String>) {

    val sumOptional = { x: Int, y: Int -> x + y}

    val sumFull: (Int, Int) -> Int = { x, y -> x + y }

    println("sumOptional(10, 15) = ${sumOptional(10, 15)}")

    println("sumFull(10, 20) = ${sumFull(10, 20)}")

}

 sumOptional(10, 15) = 25

 sumFull(10, 20) = 30 


 람다식은 하나의 파라미터를 가지는 것이 일반적입니다. 따라서 코틀린이 파라미터를 사용중인 것을 알 수 있으면 파라미터 선언을 해주지 않아도 되고 it이라는 키워드로 암시적으로 선언할 수 있습니다.


fun main(args: Array<String>) {

    val add: (Int) -> Int = { it + 1 }

    println("add(10) = ${add(10)}")

}

add(10) = 11


qualified return syntax를 사용하여 람다로부터 값을 명시적으로 리턴할 수 있습니다. 그렇게 하지 않으면 마지막 표현식의 값이 암시적으로 리턴됩니다. 따라서 다음의 isPositive와 isPositiveTest는 같습니다.


fun main(args: Array<String>) {

    val isPositive: (Int) -> Boolean = {

        val isPositive = it > 0

        isPositive

    }

    val isPositiveTest: (Int) -> Boolean = number@ {

        val isPositive = it > 0

        return@number isPositive

    }

    println("isPositive(10) = ${isPositive(10)}")

    println("isPositiveTest(-10) = ${isPositiveTest(-10)}")

}

 isPositive(10) = true

 isPositiveTest(-10) = false


+ Recent posts