# 5장 람다로 프로그래밍

### 이번장에서는...

#### 배운점, 느낀점

- 람다 식과 멤버 참조
- 함수형 스타일로 컬렉션 다루기
- 시퀀스: 지연 컬렉션 연산
- 자바 함수형 인터페이스를 코틀리에서 사용
- 수신 객체 지정 람다 사용

---

- 람다 식<sup>lambda expression</sup> 또는 람다는 기본적으로 다른 함수에 넘길 수 있는 작은 코드 조각을 뜻한다.
- 람다를 사용하면 쉽게 공통 코드 구조를 라이브러리 함수로 뽑아낼 수 있다.

### 람다 소개: 코드 블록을 함수 인자로 넘기기

- 이벤트가 발생하면 이 핸들러를 실행하자 나 데이터 구조의 모든 원소에 이 연산을 적용하자와 같은 생각을 코드로 표현하기 위해 일련의 동작을 변수에 저장하거나 다른 함수에 넘겨야 하는 경우가 자주 있다. 예전에
  자바에서는 무명 내부 클래스를 통해 이런 목적을 달성했다. 무명 내부 클래스를 사용하면 코드를 함수에 넘기거나 변수에 저장할 수 잇기는 하지만 상당히 번거롭다. 이와 달리 함수형 프로그래밍에서는 함수를 값처럼
  다루는 접근 방법을 택함으로써 이 문제를 해결한다.
- 람다를 사용하면 코드가 더욱 더 간결해진다.
- 람다 식을 사용하면 함수를 선언할 필요가 없고 코드 블록을 직접 함수의 인자로 전달할 수 있다.

#### 람다와 컬렉션

```kotlin

data class Person(val name: String, val age: Int)

fun findTheOldest(people: List<Person>) {
    var maxAge = 0
    var theOldest: Person? = null
    for (person in people) {
        if (person.age > maxAge) {
            maxAge = person.age
            theOldest = person
        }
    }
    println(theOldest)
}

fun main(args: Array<String>) {
    val people = listOf(Person("Alice", 29), Person("Bob", 31))
    findTheOldest(people)
    println(people.maxBy { it.age })
    println(people.maxBy(Person::age))
}

람다 식의 문법
{ x: Int, y: Int -> x + y }
// 파라미터           // 본문
// 항상 중괄호 사이에 위치함
val sum = { x: Int, y: Int -> x + y }
println(sum(1, 2)) // 변수에 저장된 람다를 호출한다.
run { println(42) } // 람다 본문에 잇는 코드를 실행한다.
val people = listOf(Person("Alice", 29), Person("Bob", 31))
println(people.maxBy { it.age })
println(people.maxBy(Person::age))
println(people.maxBy { p: Person -> p.age }) // 정식으로 람다를 작성하면 이와 같다.
println(people.maxBy() { p: Person -> p.age })
이름 붙인 인자를 사용해 람다 넘기
fun main() {
    val people = listOf(Person("이몽룡", 29), Person("성춘향", 31))
    val names = people.joinToString(separator = " ", transform = { p: Person -> p.name })

    println(names)

    // 괄호 밖에 전달하기
    println(people.joinToString(" ") { p: Person -> p.name })
}
람다 파라미터 타입 제거하기
println(people.maxBy { p: Person -> p.age }) // 파라미터 타입을 명시
println(people.maxBy { p -> p.age }) // 파라미터 타입을 생략(컴파일러가 추론)
println(people.maxBy { it.age }) // it은 자동 생성된 파라미터 이름이다.
여러줄
fun main(args: Array<String>) {
    val sum = { x: Int, y: Int ->
        println("Computing the sum of $x and $y ...")
        x + y
    }

    println(sum(1, 2))
}

현재 영역에 있는 변수에 접근

fun printMessageWithPrefix(messages: Collection<String>, prefix: String) {
    messages.forEach {  // 각 원소에 대해 수행할 작업을 람다로 받는다
        println("$prefix $it") // 람다 안에서 함수의 prefix 파라미터를 사용한다
    }
}

fun main(args: Array<String>) {
    val errors = listOf("403 Forbidden", "404 Not Found")
    printMessageWithPrefix(errors, "Error: ")
}
fun printProblemCounts(responses: Collection<String>) {
    var clientErrors = 0
    var serverErrors = 0
    responses.forEach {
        if (it.startsWith("4")) {
            clientErrors++
        } else if (it.startsWith("5")) {
            serverErrors++
        }
    }

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

fun main(args: Array<String>) {
    val responses = listOf("200 OK", "418 I'm a teapot", "500 Internal Server Error")
    printProblemCounts(responses)
}
멤버 참조
val getAge = Person::age
fun salute() = println("Salute!")
run(::salute) // 최상위 함수를 참조한다.

//

val action = { person: Person, message: String ->
    sendEmail(person, message) // 이 람다는 sendEmail 함수에게 작업을 위임 한다
}
val nextAction = ::seanEmail // 람다 대신 멤버 참조를 쓸 수 있다

//

val createPerson = ::Person // Person의 인스턴스를 만드는 동작을 값으로 저장한다
val p = createPerson("Alice", 29)
println(p)

//

fun Person.isAdult() = age >= 21
val predicate = Person::isAdult

컬렉션 함수형 API

filter
val list = listOf(1, 2, 3, 4)
println("list.filter{ it % 2 == 0 }") // 짝수만 남는다

data class Person(val name: String, val age: Int)

val people = listOf(Person("Alice", 29), Person("Bob", 31))
println("people.filter{ it.age > 30 }") // 30 초과 를 구해라
map
val people = listOf(Person("Alice", 29), Person("Bob", 31))
println(people.map { it.name })

people.map(Person::name)
people.filter { it.age > 30 }.map(Person::name)

// 사람들의 나이의 최댓값을 구하고 나이가 그 최댓값과 같은 모든 사람을 반환하면 된다.
people.filter { it.age == people.maxBy(Person::age)!!.age }

// 리팩터링
val maxAge = people.maxBy(Person::age)!!.age
people.filter { it.age == maxAge }
Map 의 필터와 변환 함수
val numbers = mapOf(0 to "zero", 1 to "one")
println(numbers.mapValues { it.value.toUpperCase() })
all, any, count, find: 컬렉션에 술어 적용
all: 모든 원소가 이 술어를 만족하는 지
val canBeInClub27 = { p: Person -> p.age <= 27 }

val people = listOf(Person("Alice", 29), Person("Bob", 31))
println(people.all(canBeInClub27))
any: 술어를 만족하는 원소가 하나라도 있는지
println(people.any(canBeInClub27))
val list = listOf(1, 2, 3)
println(!list.all { it == 3 }) // !를 눈치 재지 못하는 경우가 자주 있다. 따라서 이런 식보다는 any를 사용하는 식이 더 낫다

println(list.any { it != 3 }) // any를 사용하려면 술어를 부정해야한다.

첫 번째 식은 list의 모든 원소가 3인 것은 아니라는 뜻이다. 이는 list의 원소 중 적어도 하나는 3이 아니라는 말과 같다. 두 번째 식은 any를 사용해 검사한다.

count
val people = listOf(Person("Alice", 29), Person("Bob", 31))
val canBeInClub27 = { p: Person -> p.age <= 27 }
println(people.count(canBeInClub27))
find: 술어를 만족하는 원소를 하나 찾고 싶을 때
val people = listOf(Person("Alice", 29), Person("Bob", 31))
val canBeInClub27 = { p: Person -> p.age <= 27 }
println(people.find(canBeInClub27))
groupBy: 리스트를 여러 그룹으로 이뤄진 맵으로 변경
val people = listOf(Person("Alice", 29), Person("Bob", 31)...)
println(people.groupBy { it.age })
val list = listOf("a", "b", "c")
println(list.groupBy(String.first)) // first는 String의 확장함수
flatMap과 flatten: 중첩된 컬렉션 안의 원소 처리
class Book(val title: String, val authors: List<String>)
books.flatMap{ it.author }.toSet()
val strings = listOf("abc", "def")
println(strings.flatMap { it.toList() })
지연 계산(Lazy) 컬렉션 연산
people.map(Person::name).filter{ it.startsWith("A") } // 비효율

people.asSequence() //원본 컬렉션을 시퀀스로 변환한다.
  .map(Person::name)     // 시퀀스도 컬렉션과 똑같은 API를 제공한다
  .filter{ it.startsWith("A") }
  .toList() // 결과 시퀀스를 다시 리스트로 변환한다
// 중간 결과를 저장하는 컬렉션이 생기지 않기 때문에 원소가 많은 경우 성능이 눈에 띄게 좋아진다.
시퀀스 연산 실행: 중간 연산과 최종 연산
val people = listOf(Person("Alice", 29), Person("Bob", 31), Person("Charles", 31), Person("Dan", 21))
println(people.asSequence().map(Person::name)
  .filter { it.length < 4}.toList()) // map 다음에 filter 수행

println(people.asSequence().filter{ it.name.length < 4 }
  .map(Person::name).toList()) // filter 다음에 map 수행
자바 스크림과 코틀린 시퀀스 비교
시퀀스 만들기
자바 함수형 인터페이스 활용
자바 메서드에 람다를 인자로 전달

// 자바
void postponeComputation (int delay, Runnable computation)

// 코틀린
postponeComputataion(1000) { print(42) } // 프로그램 전체에서 Runnable 인스턴스는 단 하나만 만들어진다.

postponeComputation(1000, object : Runnable { // 객체 식을 함수형 인터페이스 구현으로 넘긴다
  override fun run() {
    println(42)
  }
})

val runnable = Runnuable{ println(42) }// Runnable 은 SAM 생성자 전역변수로 컴파일되므로 프로그램 안에 단 하나의 인스턴스만 존재한다
fun handleComputation(){
    postponeComputation(1000, runnable) // 모든 handleComputation호출에 같은 객체를 사용한다.
}

fun handleComputation(id: String){ // 람다 안에서 id 변수를 포획한다
    postponeComputation(1000) { println(id) } //handleComputation을 호출할 때마다 새로 Runnable 인스턴스를 만든다
}
람다의 자세한 구현