코틀린(Kotlin)

코틀린(kotlin) : Lambda 고차함수와 람다, 익명 함수

알통몬_ 2018. 2. 6. 12:51
반응형


공감 및 댓글은 포스팅 하는데

 아주아주 큰 힘이 됩니다!!

포스팅 내용이 찾아주신 분들께 

도움이 되길 바라며

더 깔끔하고 좋은 포스팅을 

만들어 나가겠습니다^^

 

이번 포스팅에서는 람다에 대해서 공부합니다.


Higher - Order Function : 고차함수

고차 함수는 메서드의 파라미터로 또 다른 메서드가 있거나, 메서드를 리턴하는

메서드를 말합니다. 좋은 예가 lock() 메서드 입니다.

fun <T> lock(lock: Lock, body: () -> T): T {
lock.lock()
try {
return body()
}
finally {
lock.unlock()
}
}

위 코드를 보게되면 body 는 () -> T 라는 타입을 가집니다.

그래서 이 body 는 파라미터를 가지지 않고 T 타입의 값을 반환하는 함수로 간주됩니다.

그리고 try{} 블록에서 호출되고, lock() 함수에 의해 결과가 반환됩니다.


만약 위 lock(...) 메서드를 호출하고 싶다면, 아래처럼 하시면 됩니다.

fun main(args: Array<String>) {
lock(lock, ::aaa)


}
fun aaa() {}



fun <T> lock(lock: Lock, body: () -> T): T {
lock.lock()
try {
return body()
}
finally {
lock.unlock()
}
}

또는 람다표현식으로 전달하는 방법도 있습니다.

lock(lock, { aaa() })


람다 표현식에 대해 간단하게 살펴보겠습니다.

- 람다 표현식은 항상 {} 중괄호 블록으로 감싸야 합니다.

- 파라미터는 '->' 전에 선언해야 합니다.

- body 는 '->' 뒤에 선언합니다.


코틀린에서는 메서드의 마지막 파라미터가 메서드이면서 람다식을 해당 인수로 전달하는

경우에는 괄호 밖에 지정할 수 있는 규칙이 있습니다.

lock(lock) {
aaa()
}


고차 함수에 대한 또 다른 예는 map() 입니다.

fun <T, R> List<T>.map(transform: (T) -> R): List<R> {
val result = arrayListOf<R>()
for (item in this)
result.add(transform(item))
return result
}

호출은 아래처럼 합니다.

fun main(args: Array<String>) {

val a = arrayListOf(1,2,3,4,5)
val doubles = a.map { value -> value * 2 }
print(doubles)

}

만약 메서드를 호출하는데, 람다식이 유일한 인수라면, 괄호는 생략될 수 있습니다.


단일 매개 변수의 암시적 이름 : it

만약 매개 변수가 하나 뿐이라면, -> 를 생략하고 it 을 사용할 수 있습니다.

ex) 아래 doubles 와 doubles2 는 완벽하게 같은 기능을 합니다.

val a = arrayListOf(1,2,3,4,5)
val doubles = a.map { value -> value * 2 }
val doubles2 = a.map { it * 2 }


이런 규칙에 따라서 LINQ-style의 코드도 작성할 수 있습니다.

val str = arrayListOf("a","b","c","d","eeeee")
print(str.filter { it.length == 5 }.sortedBy { it }.map { it.toUpperCase() })

길이가 5인 문자열만 대문자로 변환해주는 코드입니다.


사용하지 않는 변수는 밑줄로

만약 람다의 파라미터를 사용하지 않는다면, '_'(언더스코어)를 사용할 수 있습니다.

fun main(args: Array<String>) {
val map = HashMap<Int, String>()
map.put(1, "A")
map.put(2, "B")
map.put(3, "C")
map.put(4, "D")
map.forEach { _, value -> print("$value!") }
}


람다에서의 파괴 : 추후에 포스팅 합니다.


Inline Function

때때로 Inline Function을 사용해서 고차함수의 성능을 향상시키는 것이 좋습니다.


람다 표현식과 익명 메서드

람다식과 익명 메서드는 함수리터럴입니다.

즉, 선언되지 않지만, 즉시 식으로 전달되는 메서드입니다.

아래의 예를 봅시다.

max(strings, { a, b -> a.length < b.length })

max() 메서드는 고차함수입니다. 두 번째 인자가 메서드입니다.

두 번째 인자는 그 자체로 메서드이고 즉, 함수 리터럴인 표현식입니다.

메서드로써 아래와 같습니다.

fun compare(a: String, b: String): Boolean = a.length < b.length


---------------------------------------------------------------------------------------------------------


---------------------------------------------------------------------------------------------------------




Function types

메서드의 파라미터로 메서드를 받으려면, 해당 파라미터는 대한 메서드 타입을 

지정해야 합니다.

fun <T> max(collection: Collection<T>, less: (T, T) -> Boolean): T? {
var max: T? = null
for (it in collection)
if (max == null || less(max, it))
max = it
return max
}

위 max 메서드를 보면, less 파라미터의 타입은 (T, T) -> Boolean 입니다.

즉 타입이 T인 두 개의 파라미터를 가지고, Boolean 타입의 값을 반환하는 메서드 입니다.

: 첫 번째가 두 번째보다 작으면 true를 반환합니다.

{} 블록에서 4번 째 줄을 보면, less가 메서드로 사용됩니다. less(max, it)

메서드의 유형은 위처럼 작성되거나, 각 파라미터의 의미를 문서화하려는 경우에는

named 파라미터를 가질 수 있습니다.


메서드의 타입에 null 변수를 선언하려면, 메서드 전체를 () 블록으로 감싸고 ? 를 

붙이면 됩니다.

ex) var sum : ((Int, Int) -> Int) ? = null



Lambda Expression Syntax : 람다식 문법

람다식의 정석적인 문법은 아래와 같습니다.

var sum = { a : Int, b : Int -> a + b }

람다식은 항상 중괄호 블록으로 감싸집니다.

매개 변수의 선언은 중괄호 불록 안 -> 전에 오고, 본문은 -> 이후에 옵니다.

그리고 반환 값이 Unit이 아니라면 본문의 마지막 식이 반환 값으로 처리 됩니다.

그리고 아래처럼 선언할 수도 있습니다.

var sum2 : (Int, Int) -> Int = { a, b -> a + b }


람다 표현식에서는 매개 변수가 하나만 있는 것이 일반적인데요.

이런 경우에는 매개 변수를 사용하지 않고 it 을 사용합니다.

val its = arrayListOf(-1, 2, -3, 4, -5)
its.filter { it > 0 }


정규 표현식을 사용하면 명시적으로 람다 표현식의 반환 값을 지정할 수 있습니다.

그렇지 않은 경우 마지막 표현식 값이 반환 됩니다.

때문에 아래의 두 snippets는 동일합니다.

val its = arrayListOf(-1, 2, -3, 4, -5)
its.filter {
val returns = it > 0
returns
}
its.filter {
val returns = it > 0
return@filter returns
}


* 메서드가 마지막 파라미터로 다른 메서드를 사용한다면, 람다식의 인수는

() 안의 인수목록 밖에서 람다식 인수를 전달할 수 있습니다.



Anonymous Function : 익명 함수

지금까지 위에서 보았던 람다 식에서 빠진 한 가지가 있습니다.

바로 메서드의 반환 타입을 지정하는 기능입니다.

대부분의 케이스에서 반환 타입을 지정할 필요가 없습니다.

왜냐하면 반환 타입을 자동으로 유추해주기 때문입니다.

하지만, 명시적으로 반환 타입을 지정할 필요가 있다면,

익명 함수를 사용하면 됩니다.


익명 함수는 이름이 생략된다는 점을 제외하고는 일반적인 메서드의

선언과 유사합니다.

fun(a : Int, b : Int) : Int = a + b

fun(a : Int, b : Int) : Int {
return a + b
}

익명 함수는 멤버로 선언할 수는 없습니다.

파라미터와 반환 타입은 일반적인 메서드와 같은 방식으로 지정됩니다.

파라미터의 타입을 컨텍스트에서 유추할 수 있는 경우에는 생략될 수 있습니다.

its.filter { fun(item) = item > 0  }


익명 함수에 대핸 반환 타입 유추는 일반 메서드와 마찬가지로 작동합니다.

반환 타입은 표현 본문이 있는 익명 함수에 대해서는 자동으로 유추 되고,

블록 본문이 있는 익명 함수에 대해서는 명시적으로 선언되어야 합니다.


익명 함수의 매개 변수는 항상 괄호 안에서 전달됩니다.

괄호 밖에서 메서드를 그대로 두는 것을 허용하는 줄임 문법은 람다식에서만

적용됩니다.


람다 식과 익명 함수의 다른 한 가지는 non-local returns 동작입니다.

레이블이 없는 return 문은 항상 fun 키워드로 선언된 메서드 안에서 반환 됩니다.

즉, 람다 식 내부의 반환은 메서드 내부에서 반환 되고,

익명 함수 내부의 반환은 익명 함수 자체에서 반환 됩니다.



Closures

람다 식이나 익명 함수(뿐만 아니라, 로컬 메서드, object 메서드)는

closure에 접근할 수 있습니다.

즉 변수는 외부 범위에서 선언됩니다.

자바와는 다르게, 클로저에서 capture된 변수는 수정될 수 있습니다.

fun main(args: Array<String>) {
val its = arrayListOf(-1, 2, -3, 4, -5)
var sum = 0
its.filter {
it > 0
}.forEach { sum += it }
print(sum)
}


Function Literals with Receiver : 리시버가 있는 메서드 리터럴

코틀린에서는 지정된 리시버 오브젝트로 메서드 리터럴을 

호출할 수 있는 기능을 제공합니다.

메서드 리터럴의 body 안에서, 추가적인 한정자 없이, 리시버 오브젝트에 대한

메서드를 호출할 수 있습니다.

사용 법의 가장 중요한 예는 Type-safe Groovy-style Builders입니다.


아래와 같은 메서드 리터럴은 리시버를 가지는 메서드 타입입니다.

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

메서드 리터럴은 리시버 오브젝트의 메서드인 것처럼 호출할 수 있습니다.

1.sum(2)


익명 함수 구문을 사용하면 메서드 리터럴의 리시버 타입을 직접 지정할 수 있습니다.

이 것은 리시버로 메서드 타입의 변수를 선언하고 나중에 사용해야 하는 경우에

유용할 수 있습니다.

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



이상입니다.

다음 포스팅에서는 Inline Functions 에 대해서 공부합니다.






반응형