본문 바로가기
프로그래밍 놀이터/Kotlin, Coroutine

[Kotlin Tutorial] 한 차원 높은 함수 : 람다를 parameter 와 return value 로 - Chap8. Higher-order functions: lambdas as parameters and return values

by 돼지왕 왕돼지 2017. 8. 31.
반응형

 [Kotlin Tutorial] 한 차원 높은 함수 : 람다를 parameter 와 return value 로 - Chap8. Higher-order functions: lambdas as parameters and return values


참조 : Kotlin in action

3rd party lib, Anonymous Class, anonymous function, Anonymous functions: local returns by default, Argument, argumnet, auto inline, bytecode, bytecode size, Calling functions passed as arguments, CLASS, closable resource, code block, collection operation, Compile, Control flow in higher-order functions, database transaction, Debugger, Deciding when to declare functions as inline, Default and null values for parameters with function types, default value, extension function, file, Filter, final operation, for loop, for-each, function call, function name as label, Function Reference, function type, function type parameter, function type syntax, function types, Function0, Function1, FunctionN interface, higher-order function, How inlining works, illegal usage of inline-parameter, inline, inline function, Inline functions: Removing the overhead of lambdas, inline modifier, Inlining collection operations, invoke method, iteration, java7, jvm, jvm auto inline, Kotlin, kotlin basics, kotlin tutorial, label@, LAMBDA, lambda assign, lambda field 저장, lambda function, lambda inline, large collection, local return, LOCK, machine code, map, Memory, noinline, noinline modifer, non-local return, nullable, nullable function type, Operation, outer function, outer scope, overhead, Param, parameter, parameter name, parameter types -> return type, Performance, receiver, Reference, Removing duplication through lambdas, Resource, Restrictions on inline functions, return function type, return inside lambda, Return statements in lambdas: return from an enclosing function, return value, return with label, Returning from lambdas: return with a label, Returning functions from functions, SEQUENCE, sequence map inline, StackTrace, terminal operation, this@label, try finally, try-with-resource, unit return type, Use, Using function types from Java, Using inlined lambdas for resource management, variable, [Kotlin Tutorial] 한 차원 높은 함수 : 람다를 parameter 와 return value 로 - Chap8. Higher-order functions: lambdas as parameters and return values, 리소스 관리, 작은 코드 규모, 코틀린, 코틀린 강좌, 코틀린 기초 강좌


8.1. Declaring higher-order functions


-

Higher-order function 이란 argument 와 return 으로 다른 function 을 갖는 것을 의미한다.

Kotlin 에서는 function 이 lambda 나 function reference 로 표시된다.




8.1.1. Function types


-

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

val action = { println(42) }


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

val action:() -> Unit = { println(42) } // 여기서는 Unit 을 생략할 수 없다.


function type 을 쓰는 syntax 는 “(Parameter types) -> Return type” 이다.



-

var funOrNull: ((Int, Int) ->Int)? = null

// function type 을 nullable 로



-

fun performRequest(url:String, callback:(code:Int, content:String) -> Unit){

    ...

}


val url = “http://kotl.in"

performRequest(url) { code, content -> /* … */ } // param name 을 그대로 써도 되고

performRequest(url) { code, page -> /* … */ } // param name 을 바꾸어도 된다.




8.1.2. Calling functions passed as arguments


-

일반 함수 호출하듯 호출하면 된다.



-

IDE 에서 debugger 와 연동이 잘 되서 해당 lambda function 이 불릴때 focus 가 따라간다.




8.1.3. Using function types from Java


-

function type 은 사실은 일반적인 interface 와 동일하다.

function type variable 은 Kotlin 의 FunctionN interface 를 구현한 것이다.


Function0<R> : argument 가 없다.

Function1<P1, R> : 1개의 argument



-

각 FunctionN interface 는 1개의 invoke method 가 있다.



-

/* Kotlin */

fun processTheAnswer(f: (Int) -> Int){

    print(f(42))

}


/* Java 8, 자동으로 FunctionN interface 로 변환됨 */

processTheAnswer(number -> number + 1 );


/* Java under 8 */

processTheAnswer( new Function1<Integer, Integer>(){

    @Override

    public Integer invoke(Integer number){

        return number + 1;

    }

});



-

Java 에서 Kotlin 에서 정의한 higher-order function 을 쓸 때는 Unit return type 에 대해 명시적으로 return 해줘야 한다. Unit 은 Kotlin 에서 정의한 녀석이기 때문이다.

/* Java */

List<String> strings = new ArrayList();

strings.add(“42”);

CollectionsKt.forEach(strings, s->{

    System.out.println(s);

    return Unit.INSTANCE;

});


(String) -> Unit 형태의 function type 에 void 를 return 하는 lambda 를 넘길 수 없다.




8.1.4. Default and null values for parameters with function types


-

function type 을 정의할 때 default value 를 지정할 수 있다.

그리고 nullable 로 둘 수도 있다.




8.1.5. Returning functions from functions


-

이 요구사항이 필요한 경우는 많지 않지만, 가끔은 아주 유용할 수 있다.

return type 으로 function type 을 정의하고 그냥 function 을 return 하면 된다.




8.1.6. Removing duplication through lambdas





8.2. Inline functions: Removing the overhead of lambdas


-

Lambda 는 대부분 anonymous class 로 compile 된다.

Lambda 를 쓰면서 기존 Java 코드보다 class 를 훨씬 더 만드는 것이 아닐까 걱정할 수 있다. 게다가 해당 class 들이 outer scope 의 변수까지 참조하면 memory 를 더 많이 사용할 수 있다.

"inline” modifier 를 쓰면 실제 anonymous class 생성 대신 실제 코드 block 으로만 compile 되도록 할 수 있다.




8.2.1. How inlining works


-

inline fun <T> synchronized(lock:Lock, action:() -> T): T{

    lock.lock()

    try{

        return action()

    } finally{

        lock.unlock()

    }

}


fun foo(l: Lock){

    println(“Before sync”)

    synchronized(l){

        println(“Action”)

    }

    println(“After sync”)

}


// compiled code

fun __foo__(l: Lock){

    println(“Before sync”)

    l.lock()

    try{

        println(“Action”)

    } finally{

        l.unlock()

    }

    println(“After sync”)

}



-

inline function 은 그냥 호출할 수도 있고, functionType 으로써 param 으로 넘길 수도 있다.




8.2.2. Restrictions on inline functions


-

일반적으로 function 이 inline 으로 되어있으면 전달되는 lambda 는 자동으로 inline 된다.

그러나 모든 전달되는 lambda 가 inline 될 수 있는 것은 아니다.

추후 이용하기 위해 variable 에 lambda 를 assign 하는 경우에는 이 녀석은 inline 될 수 없다.


일반적으로 param 으로 넘어온 lambda 가 바로 호출되거나 다른 inline function 으로 전달되는 경우는 inline 될 수 있다.

그 외의 경우에는 compiler 가 “Illegal usage of inline-parameter” 에러를 뿜는다.



-

Sequence 의 경우는 map 이 inline 이 아닌데, 해당 lambda 를 저장하고 있다가 final operation 이 수행되었을 때 이 lambda 를 사용해야 하기 때문이다.



-

inline 시킬 수 없는 lambda 의 경우 “noinline” modifier 를 사용하면 된다.



-

compiler 는 모듈간, 3rd-party lib 의 function 에도 inline 을 지원한다.

inline function 을 Java 에서 호출할 수도 있다.

Java 에서 호출한 경우는 inline 되지 않고 일반 function call 처럼 작동한다.






8.2.3. Inlining collection operations


-

collection 의 filter function 은 inline 으로 정의되어 있다.

이 말은 filter 함수 뿐만 아니라 param 으로 넘어오는 lambda 도 inline 이 된다.


그래서 for-each 방식으로 iteration 을 도는 것과 performance 차이는 없다.



-

sequence 를 사용하는 경우는 inline 되지 않는다.

모든 중간 과정의 sequence 는 lambda 를 field 로 저장하는 object 이다. 그리고 terminal operation 이 수행되면 이 lambda 들을 불러다 로직을 수행한다.


그래서 large collection 에만 sequence 를 사용하는 것이 좋고, 작은 collection 에는 그냥 바로 collection operation 을 수행하는 것이 좋을 수 있다.




8.2.4. Deciding when to declare functions as inline


-

모든 곳에 inline 을 넣는 것이 좋은 것은 아니다.

inline 은 lambda 를 argument 로 받는 케이스에만 보통 performance 상승 효과가 있다.

다른 케이스에는 생각을 해보아야 한다.



-

일반적인 function call 에 대해서 JVM 은 inline 을 지원한다.

code 실행을 분석한 후에 필요한 경우 inline 을 만들어버린다.

이는 bytecode 를 machine code 로 만들 때 자동으로 수행된다.

bytecode 에서는 각 함수들이 inline 처럼 각 part 에 계속 중복해서 들어갈 필요가 없다.

게다가 stacktrace 도 function call 인 경우에 더 깔끔하게 나온다.



-

lambda 가 argument 로 들어왔을 때는 inline 을 통해 overhead 를 많이 줄일 수 있다.

JVM 이 현재는 필요한 경우에 알아서 inline 을 할 정도로 똑똑하지는 않다.

나중에 다루는 내용이지만 inline 을 사용하면 regular lambda 로는 할 수 없는 일을 가능하게 한다.



-

inline 을 쓰는 것이 무조건 능사가 아닌 것이,

코드 규모가 큰 경우 호출하는 쪽에 모두 inline 하면 bytecode size 가 많이 늘어날 수 있다.

Kotlin 에서 지원하는 inline function 을 사용할 때는 항상 작은 규모의 코드만 써야 함을 기억해야 한다.




8.2.5. Using inlined lambdas for resource management


-

lambda 는 리소스 관리에 유용하게 쓰일 수 있다.

리소스 관리라 하면, operation 전에 resource 를 얻고 operation 후에 resource 를 release 하고..

리소스는 file, lock, database transaction 등 여러 가지가 될 수 있다.

보통 이 경우에 try/finally 구조를 많이 사용한다.



-

lock 을 잡고 해제하는 것은 withLock 을 통해 가능하다

val l: Lock = …

l.withLock{


}



-

Java 7 에 try-with-resource 를 쓸 수 있는데 Kotlin 에서는 동일한 것을 지원하지 않는다.

lambda 방식을 쓰면 일관성을 갖고 처리할 수 있기 때문이다.

use 를 쓰면 된다. ( use 는 closable resource 에 extension function 으로 구현되어 있다. )

fun readFirstLineFromFile(path: String): String{

    BufferedReader(FileReader(path)).use{

        br -> return br.readline() // 요 return 은 function return 이 된다.. 이는 나중에 다룬다.

    }

}


use 는 exception 이 있던 없던 resource 를 잘 정리해준다.

그리고 use 는 inline 으로 되어 있기 때문에 안에서 많은 일을 하면 안 좋다.





8.3. Control flow in higher-order functions


8.3.1. Return statements in lambdas: return from an enclosing function


-

data class Person(val name: String, val age: Int)


val people = listOf(Person(“Alice”, 29), Person(“Bob”, 31))


fun lookForAlice(people: List<Person>){

    people.forEach{

        if (it.name == “Alice”){

            println(“Found!”)

            return

        }

    }

    println(“Alice is not found”)

}


Lambda 안에서 return 을 하면 lambda 를 호출한 함수를 return 하게 된다.

이런 경우를 non-local return 이라고 부른다.



-

outer function 의 return 이 가능한 경우는 function 이 lambda 를 argument 로 가지면서 inline 되었을 경우만이다.

다시 말해 위의 코드에서는 forEach 가 inline function 이기 때문에 가능하다는 것.




8.3.2. Returning from lambdas: return with a label


-

lambda 에서 local return 도 가능하다.

for loop 의 break 와 비슷한 느낌이라고 받아들이면 된다.

non-local return 과 구분하기 위해 label 을 사용한다.

fun lookForAlice(people: List<Person>){

    people.forEach label@{

        if (it.name == “Alice”)

            return @label

    }

    println(“Alice might be somewhere”)

}


lambda open 전이 label@ 을 넣어주면 된다.

그리고 return@label 의 형태로 써주면 된다. ( label 이 아니라 다른 이름 당연 가능하다 )



-

lambda 를 취하는 function name 을 label 로 사용도 가능하다.

fun lookForAlice(people: List<Person>){

    people.forEach{

        if(it.name==“Alice”)

            return forEach

    }

    println(“Alice might be somewhere”)

}


label@ 이 정의된 경우는 이렇게 function name 으로 return 할 수 없다.



-

println(StringBuilder().apply sb@{

    listOf(1,2,3).apply{

        this@sb.append(this.toString())

    }

})


원래 this 는 가장 가까운 범위에 있는 receiver 를 가르키는데,

this@label 을 통해서 상위의 것에도 접근할 수 있다.




8.3.3. Anonymous functions: local returns by default


-

fun lookForAlice(people: List<Person>){

    people.forEach(fun (person){

        if (person.name == “Alice”) return

        println(“${person.name} is not Alice”)

    })

}


anonymous function 은 name 과 parameter type 이 빠졌다는 것을 제외하면 일반 function 과 비슷하다.

anonymous function 을 일반 function 과 마찬가지로 return 을 꼭 넣어줘야 한다. (expression 에서는 return 을 생략할 수 있다.)



-

anonymous function 의 return 은 해당 function 을 벗어나는 것이다.

anonymous function 은 regular function 과 비슷해보이지만, 실상은 lambda expression 의 다른 형태이다.





8.4. Summary

-

Function type 은 variable, parameter, return value 가 될 수 있다. function 에 대한 reference 를 담는다.



-

Higher-order function 은 function 을 argument 로 받거나 return 하는 function 을 이야기한다.

function type 을 이용해서 이를 만들 수 있다.



-

inline function 이 compile 되면 bytecode 에서는 해당 function 과 전달된 lambda 가 함께 inline 이 된다.

그래서 overhead 없이 사용할 수 있다.



-

Higher-order function 은 generic library 와 같은 것들을 만들고 코드를 재사용하는데 엄청나게 기여할 수 있다.



-

Inline function 은 non-local return 을 지원한다.

non-local return 이란 lambda 안에서 return 을 하는 것인데 그 return 이 function return 으로 동작하는 것이다.



-

Anonymous function 은 lambda 를 다른 syntax 로 사용할 수 있게 하는 것이다.

return 에 대해 lambda 와는 달리 anonymous function 을 나가게 된다.





반응형

댓글