[Kotlin Tutorial] 한 차원 높은 함수 : 람다를 parameter 와 return value 로 - Chap8. Higher-order functions: lambdas as parameters and return values |
참조 : Kotlin in action
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 을 나가게 된다.
댓글