Lambda, Closure

김건우·2023년 12월 15일

개발 공부

목록 보기
5/13
post-thumbnail

목차

  • Lambda 람다 식
  • 기본 구성
  • 람다 인자가 하나일 때 it 사용
  • 후행 람다, Trailing lambda
  • 사용하지 않는 람다 인자는 '_' 로 처리
  • 람다로 구조분해하기, Destructing
  • 익명 함수
  • 클로저, Closures
  • 확장 람다, 수신 객체

참고


람다 식, lambda expression

  • 람다는 부가적인 장식이 최소한으로 들어간 코드이다.
  • 함수 리터럴 (function literal) 이라고 부르기도 한다.
  • 람다는 식(expression) 으로, 반환(return) 값을 가질 수 있다.
    lambda 식의 반환 타입이 Unit (Java의 void와 비슷하다) 이라면 반환 값이 없다.

기본 구성

val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y}
  • { }안에 작성되는게 람다 식이다.

  • = 앞은 프로퍼티 선언이고, = 뒤는 { 인자1, 인자2 -> 함수 내용 } 으로 구성되어있다.

  • -> 뒤에 함수 내용(반환 값)을 작성한다.

    val sum = { x: Int, y: Int -> x + y }
  • 람다 식 안에서 반환 타입을 추론할 수 있으면, 반환 타입을 명시하지 않아도 된다.


람다 인자가 하나일 때 it 사용

val list = listOf(1, 2, 3, 4)
println( list.map{ n -> "[$n]" } )
결과 : [[1], [2], [3], [4]]
  • 람다 내부에서 사용하는 인자가 하나일 경우, 코틀린은 자동으로 인자 이름을 it 으로 만든다.
  • 이 경우, 원래의 인자 -> 함수 내용 구성을 it을 사용한 함수 내용 으로 간소화할 수 있다.

map 사용

val list = listOf(1, 2, 3, 4)
println( list.map{ "$it" } )
결과 : [1, 2, 3, 4]
val list = listOf('a', 'b', 'c', 'd')
val result = list.map{"${it.uppercaseChar()"}
결과 : [A, B, C, D]

filter 사용

val ints = listOf(-3, -1, 1, 3, 5)
println(ints.filter { it > 0 })
결과 : [1, 3, 5]

후행 람다, Trailing lambda

  • 함수가 여러 인자를 받고 람다가 마지막 인자인 경우, 람다를 함수의 인자 목록을 감싼 괄호 다음에 위치시킬 수 있다.
    val list = listOf(9, 11, 23, 32)
    println( list.joinToString(" ") { "$it" } )
    결과 : 9 11 23 32
  • joinToString() 의 마지막 인자를 람다 로 지정해 list의 각 원소를 String으로 반환하고, 변환한 String 사이에 " " 를 붙여 하나로 합쳐 반환한 식이다.

LINQ 스타일 코드


사용하지 않는 람다 인자는 '_'로 처리

  • 람다 안에 인자들 중 사용하지 않을 인자는 _ 로 표시한다.
    val map = mapOf<Int, String>(1 to "one", 2 to "two", 3 to "three")
    map.forEach { (_, value) -> println("$value!") }
    결과 : 
    one!
    two!
    three!

람다로 구조분해하기, Destructing

val map = mapOf<Int, String>(1 to "one", 2 to "two", 3 to "three")
   println(map.mapValues { entry -> "${entry.value}!" })
   println(map.mapValues { (key, value) -> "$value!" })
결과 : 
{1=one!, 2=two!, 3=three!}
{1=one!, 2=two!, 3=three!}
  • 두 println문은 .mapValues 함수로 map 의 pair<Int, String>를 key=value! 라는 값으로 분해한다.
    { a -> ... } 				// 인자 한 개 
    { a, b -> ... } 			// 인자 두 개
    { (a, b) -> ... } 			// 분리된 Pair
    { (a, b), c -> ... } 		// 분리된 Pair 와 다른 인자

익명 함수

  • 반환 타입 을 자동으로 추론할 수 있지만, 굳이 명시해야 하는 경우에 익명 함수 를 쓸 수 있다.

    fun(x: Int, y: Int): Int = x + y
  • 정식 함수 선언처럼 보이지만, 함수 이름이 생략되었다. 함수 블록 안에 함수 내용을 선언할 수 있다.

    fun(x: Int, y: Int): Int {
       return x + y
    }
  • 함수 파라미터의 반환 타입 또한 함수 내용에서 추론 가능하면 생략할 수 있다.

    ints.filter(fun(item) = item > 0)

    람다와 익명 함수의 return 목적지 차이

    return 은 따로 달아놓은 label이 없는 경우 가장 주변의 fun 키워드가 있는 요소로 이동한다.
    람다는 사용할 때 fun 키워드를 따로 쓰지 않기 때문에 람다 밖에 있는 fun 을 찾아 return하게 되고, 익명 함수는 fun 키워드를 사용하기 때문에 익명함수 자체에서 끝난다.


클로저, Closures

  • 현재 요소의 범위에 들어있지 않은 변수들을 포착해 사용하는 함수이다.
  • 람다 식은 자신 바깥의 변수들을 사용할 수 있고, 해당 경우에 람다도 클로저라고 부를 수 있다.
    var sum = 0
    ints.filter { it > 0 }.forEach {		// 람다 내에서
        sum += it							// 자유변수 sum을 포착해 사용
    }
    print(sum)

확장 람다, 수신 객체

val sum: Int.(Int) -> Int = { other -> plus(other) }
println(5.sum(6)) 				// 5 + 6
결과 : 11
  • plus가 수신 객체(프린트문에서 5)에서 호출되는 함수 리터럴이다.

    val sum = fun Int.(other: Int): Int = this + other
    println(5.sum(6)) 				// 5 + 6
    결과 : 11
  • 위의 함수와 같은 결과를 내놓는다.

  • 익명 함수를 위와 같이 사용하면 함수 리터럴의 수신 타입을 명확하게 할 수 있다.

  • 함수를 선언하면서 수신 객체의 타입을 지정하고, 나중에 사용하려할 때 유용하게 쓸 수 있다.

    val va: (String, Int) -> String = { str, n ->
    	str.repeat(n) + str.repeat(n)
    }
    val vb: String.(Int) -> String = {
    	this.repeat(it) + repeat(it)
    }
    va("lambda", 2)      결과값 : lambdalambda
    "lambda".vb(2) 	  결과값 : lambdalambda
  • va와 vb는 똑같은 결과를 내놓는다.

  • vb는 String 파라미터를 괄호 밖으로 옮겨서 String.(Int)로 확장 함수 구문을 사용한다.
    여기서 확장 대상 객체(여기서는 String)가 수신 객체가 되고, this를 통해 수신 객체에 접근할 수 있다.

  • vb의 첫 번째 호출은 this.repeat(it)으로 this를 사용해 명시적인 형태를 사용한다.
    두 번째 호출은 this를 생략한 repeat(it) 형태로 되어 있다.
    인자가 Int 하나 뿐이기 때문에 it 으로 가리킬 수 있다.

profile
즐겁게

0개의 댓글