코틀린에서의 람다 문법과 사용법

2018, Jun 23    

소개

람다 식lamda expression 또는 람다는 기본적으로 다른 함수에 넘길 수 있는 알고리즘(코드블럭,메소드? 등등..)을 의미한다.
프로그래밍에서 이벤트 발생시 실행되어야 할 일련의 동작(알고리즘)을 변수에 저장하거나 인자로 넘겨 실행하고자 하는(javascript의 callback함수와 같은) 요구나 시도가 증가 되었으며 함수형 프로그래밍은 함수를 값처럼 다루는 접근 방법을 택함으로써 이문제를 해결하고 있다.
이런 함수형 프로그래밍에서는 익명 함수(javascript)나 익명클래스를 인자로 넘기는 코드를 보다 간결하게 표현하기 위해서 람다식을 채택하고 있다.


람다 문법

lamda expression

자바의 람다 문법과 다른점
* 코틀린 람다식은 항상 중괄호로 둘러싸여 있다.
* 인자 목록에 괄호’()’가 없다. - ()를 넣을 경우 syntax 오류


{(x:Int, y:Int) -> x+y} //<-- 인자목록에 괄호()를 넣으면 syntax 오류 발생
  • 화살표 ‘->’가 parameter와 람다본문을 구분해 준다.
  • 아래와 같이 람다식을 변수에 저장 할 수 있다.
  val sum = {x:Int, y:Int -> x+y}
  println(sum(1,2))

코틀린에서의 편의성
코틀린에서는 함수 호출 시 맨 뒤에 있는 인자가 람다 식이면 람다를 괄호 밖으로 뺄 수 있다.

val people = listOf(Person("Allice", 29),Person("Bob", 31))

people.maxBy({p:Person -> p.age})

//maxBy 함수의 유일한 인자이자 마지막 인자이므로
// 아래와 같이 괄호 밖으로 람다를 뺄 수 있음
people.maxBy() {p:Person -> p.age}

또한 람다가 유일한 인자인 경우 밖으로 뺀 람다식 앞의 빈 괄호를 없애도 된다.

people.maxBy{p:Person -> p.age}

타입 추론을 통한 파라미터 타입 생략

people.maxBy{p:Person->p.age}
people.maxBy{p->p.age} // <-- 파라미터 타입을 생략(컴파일러가 추론)

람다식 안에서 인자를 가르키는 파라미터명을 default로 it으로 지칭한다.

people.maxBy{it.age}

파라미터가 여러개 있거나 람다식이 중첩되는 경우는 각각의 it이 가라키는 파라미터를 인식하는데 어려움이 있을 수 있으므로, 파라미터가 하나 뿐이고 컴파일러가 타입을 추론할 수 있는 경우 쓰는 것이 좋다.



람다 캡처링capturing(포획)


람다 본문 블럭 내에서 외부 함수의 로컬 변수나 글로벌 변수등을 사용할 수 있는데, 이것을 lamda capturing(포획)이라고 한다.

  fun printMessageWithPrefix(messages: Collection<String>, prefix: String) {
   messages.forEach {
     println("$prefix $it")
   }
  }

자바와 달리 코틀린 람다 안에서는 final 변수가 아닌 본래의 변수에 접근이 가능하며, 변경도 가능하다.

fun printMessageWithPrefix(messages: Collection<String>, prefix: String) {

  var clientErrors = 0
  var serverErrors = 0

  messages.forEach {
     if(it.startsWith("4")) {
        clientErrors++          //lamda capture
     }else if(it.startsWith("5") {
        serverErrors++      //lamda capture
     }
  }

  println("$clientErrors client errors, $serverErrors server errors")
}

포획한 변수 수정이 가능한 코틀린에서의 람다
java에서는 함수의 생명주기가 람다 본문의 생명주기 보다 먼저 끝날 경우를 대비하여 포획한 변수를 final로 변환해 버린다.
따라서 변경이 어려운데, 코틀린에서는 어떻게 가능할까?

코틀린에서는 포획한 변수를 field로 하는 클래스를 만들어 그 class만 final로 선언한다. 이렇게 하면 클래스의 인스턴스는 final이지만 내부 field값은 변경이 가능하다. 당연히 글로만 봐서는 이해가 안될테니 코드를 보자.

//코틀린 코드
var counter = 0
val inc = { counter++ }

//실제 돌아가는 코드
class Ref<T>(var value: T)


>>> val counter =Ref(0) //<---- 변경가능한 field를 가진 class의 인스턴스를 만들어 final로 선언

>>> val inc = {counter.value++} //클래스의 인스턴는 final이므로 변경할수 없지만 field는 변경 가능

람다의 Thread-Safe
람다를 이벤트 핸드러나 다른 비동기적으로 실행되는 코드로 활용하는 경우 함수 호출이 끝난 다음에 로컬 변수가 변경 될 수도 있다. 역시 예를 보자.

//항상 0을 반환하게 되는 함수 예
fun tryToCountButtonCliks(button: Button):Int {
   var clicks = 0
   button.onClick {clicks++}
  return clicks
}