[Kotlin] Lambdas

코랑·2023년 5월 1일
0

android

목록 보기
12/16

상위 함수와 람다

코틀린의 함수는 first-class 이다.
변수와 데이터 구조에 저장될 수 있고, 다른 상위 함수에 인자로 전달되거나, 다른 상위 함수들로부터 반환될 수 있다.함수가 아닌 다른 값에 대해 가능한 모든 연산을 수행할 수 있음.
이를 용이하게 하기 위해 정적 type 프로그램 언어인 코틀린은 함수 유형들을 사용하여 함수를 표현하고 람다식과 같은 특수한 언어 구조 집합을 제공함.

상위 함수

상위 함수는 함수를 변수 혹은 반환값으로 가지는 함수이다.
좋은 예시가 collections에 fold 함수.

fun <T, R> Collection<T>.fold(
    initial: R,
    combine: (acc: R, nextElement: T) -> R // function 유형
): R {
    var accumulator: R = initial
    for (element: T in this) {
        accumulator = combine(accumulator, element)// 상위(high order) function에서 실행
    }
    return accumulator
}

// fold 호출
val items = listOf(1, 2, 3, 4, 5)

items.fold(0, { // 람다 식
    // 변수랑 변수타입 뒤에 '->'
    acc: Int, i: Int -> 
    print("acc = $acc, i = $i, ") 
    val result = acc + i
    println("result = $result")
    // 마지막줄이 반환값~
    result
})

// 변수 타입이 유추 가능하면 아래처럼 생략 가능
val joinedToString = items.fold("Elements:", { acc, i -> acc + " " + i })

// 함수 참조는 고차함수 호출을 위해 사용되기도 함
val product = items.fold(1, Int::times)

함수 유형

  1. (Int) -> String 요런식으로 함수유형을 사용함.기본 표현법은 괄호 안에 변수 화살표 뒤에 반환 타입을 나타냄.파라미터는 없을 수 있는데 반환타입은 Unit이어도 생략 불가능
  2. receiver 타입을 옵셔널하게 가질수 있음. => A.(B) -> C 이게 된다는 말.(뒤에서 나옴)
  3. suspend () -> Unit or suspend A.(B) -> C 가능
  • 함수 변수 이름도 정해줄 수 있음. (x: Int, y: Int) -> Point
  • 함수 유형을 nullable로 하고싶으면? ((Int, Int) -> Int)? 요런식으로 괄호로 감싸서 ?붙이면 됨.
  • (Int) -> ((Int) -> Unit)요것도 가능.(동일하게 (Int) -> (Int) -> Unit 가능)
  • typealias를 사용해서 타입을 대채할 수도있음.
typealias ClickHandler = (Button, ClickEvent) -> Unit

함수 타입 인스턴스화

함수 타입의 인스턴스를 얻는 방법
1. 함수 리터럴 내의 아래 형식들 중 한가지의 코드블록을 사용함

  • 람다 식 { a, b -> a + b }
  • 익명 함수 fun(s: String): Int { return s.toIntOrNull() ?: 0 }
  1. 기존 정의된 것에 호출 가능한 레퍼런스 사용
  • top-level, 로컬, 멤버, 혹은 확장 함수:: ::isOdd String::toInt
  • top-level, 멤버, 혹은 확장 변수: List<Int>::size
  • 상수: ::Regex
  1. 인터페이스로 함수 유형을 구현하고있는 커스텀 클래스의 인스턴스 사용
class IntTransformer: (Int) -> Int {
    override operator fun invoke(x: Int): Int = TODO()
}

val intFunction: (Int) -> Int = IntTransformer()
// 정보가 충분히 있으면, 컴파일러는 함수유형을 유추할 수 있음
val a = { i: Int -> i + 1 } // The inferred type is (Int) -> Int
  • literal이 아닌 함수의 값이 receiver가 있든 없든 맞교환이 가능하다 그래서 receiver가 첫 변수를 대채할 수 있으고 그 반대도 가능하다.
    예를 들어, (A, B) -> C 타입의 값이 예상되는 곳에 A.(B) -> C 타입의 값을 전달하거나 할당할 수 있으며, 그 반대의 경우도 된다..? 이게 무슨말이냐
val repeatFun: String.(Int) -> String = { times -> this.repeat(times) }
val twoParameters: (String, Int) -> String = repeatFun // OK

fun runTransformation(f: (String, Int) -> String): String {
    return f("hello", 3)
}
val result = runTransformation(repeatFun) // OK

함수타입 인스턴스 호출

val stringPlus: (String, String) -> String = String::plus
val intPlus: Int.(Int) -> Int = Int::plus
// 함수 실행하는 두가지 방법
println(stringPlus.invoke("<-", "->"))
println(stringPlus("Hello, ", "world!"))
 수신자가 있는 함수 타입의 값을 호출하는 또 다른 방법은 값이 확장 함수인 것처럼 수신자 객체를 앞에 붙이는 것입니다.
 
// receiver type은 첫번째 인자로 넘겨야함 
println(intPlus.invoke(1, 1))
println(intPlus(1, 2))
// receiver를 호출하는 또다른 방법(extension 처럼)
println(2.intPlus(3))

람다 표현식과 익명함수

람다식과 익명함수는 함수 리터럴이다. 함수 리터럴은 즉각적으로 전달되는 표현식이다. 정의되어있는것이 아닌 그런 함수이다.

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

max함수는 두번째 인자로 함수값을 받는 상위 함수이다. 이 두번째 인자는 그것 스스로 함수인 표현식이다.(함수 리터럴이라고 불리는) 위 예시랑 아래 예시랑 같음

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

람다 표현식 문법

// 람다 정의 방법
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
  • 람다 표현식은 항상 중괄호로 감싸져있다
  • 전체

Tailing lambdas 전달

// 마지막 요소가 람다일 때 아래처럼 표현 가능
val product = items.fold(1) { acc, e -> acc * e }
// 변수가 람다식 뿐이면 괄호 생략하고 이렇게 사용가능.
run { println("...") }

it: 변수가 하나일 때 암시적 이름

ints.filter { it > 0 } // this literal is of type '(it: Int) -> Boolean'

람다 표현식에서 값 반환

명시적으로 값을 반환할 수 있음 반면에 마지막 표현식의 값이 암묵적으로 반환될 수도 있음.

ints.filter {
    val shouldFilter = it > 0
    shouldFilter
}

ints.filter {
    val shouldFilter = it > 0
    return@filter shouldFilter // qualified return 
}

사용안하는 변수 밑줄

map.forEach { (_, value) -> println("$value!") }

익명함수

위 람다 표현식에는 한가지가 빠져있는데 함수의 반환타입에 대한 구체화이다.
유추가 가능하면 반환 타입을 정의 안해도 됨.
근데 명시적으로 하는게 필요하다면 익명함수 문법으로 함수 타입 정의에 사용한다

fun(x: Int, y: Int): Int = x + y
fun(x: Int, y: Int): Int {
    return x + y
}

익명 함수의 리턴 타입을 유추하는 과정은 일반 함수와 동일합니다: 본문식(expression)이 익명 함수의 리턴 타입은 자동적으로 유추될 수 있습니다. 본문이 블록문(body/statement)인 익명 함수의 리턴 타입은 명시적으로 지정되어야 하며 그렇지 않으면 Unit으로 가정됩니다.

익명 함수의 매개변수는 항상 소괄호 안에서 전달되며, 함수를 소괄호 밖에 둘 수 있는 경우는 람다식에만 적용된다는 사실을 주의해야합니다.

람다식과 익명 함수의 또 다른 차이점은 둘 다 비지역 반환을 한다는 것입니다. label이 없는 return문은 항상 fun 키워드로 선언된 함수에서 반환됩니다. 즉, 람다식 내부의 return은 식을 둘러싸는 함수 안에서 반환되고, 익명함수의 return은 익명 함수 자기 자신에서 반환됩니다.

클로저

람다 표현식 또는 익명 함수(로컬 함수 및 객체 표현식도 마찬가지)는 외부 범위에서 선언된 변수를 포함하는 클로저에 액세스할 수 있습니다. 클로저에 캡처된 변수는 람다에서 수정할 수 있습니다.

var sum = 0
ints.filter { it > 0 }.forEach {
    sum += it
}
print(sum)

0개의 댓글