# 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))
}
{ it.maxBy }
는 바로 비교에 사용할 값을 돌려주는 함수다. 이 코드는 컬렉션의 원소를 인자로 받아서(it이 그 인자를 가리킨다) 비교에 사용할 값을 반환한다.{ 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은 자동 생성된 파라미터 이름이다.
로컬 변수처럼 컴파일러는 람다 파라미터의 타입도 추론할 수 있다. 따라서 파라미터 타입을 명시할 필요가 없다.
파라미터 중 일부의 타입은 지정하고 나머지 파라미터는 타입을 지정하지 않고 이름만 남겨둬도 된다.
컴파일러가 파라미터 타입 중 일부를 추론하지 못하거나 타입 정보가 코드를 읽을 때 도움이 된다면 그렇게 일부 타입만 표시하면 쳔하다.
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
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 초과 를 구해라
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 }
val numbers = mapOf(0 to "zero", 1 to "one")
println(numbers.mapValues { it.value.toUpperCase() })
val canBeInClub27 = { p: Person -> p.age <= 27 }
val people = listOf(Person("Alice", 29), Person("Bob", 31))
println(people.all(canBeInClub27))
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를 사용해 검사한다.
val people = listOf(Person("Alice", 29), Person("Bob", 31))
val canBeInClub27 = { p: Person -> p.age <= 27 }
println(people.count(canBeInClub27))
val people = listOf(Person("Alice", 29), Person("Bob", 31))
val canBeInClub27 = { p: Person -> p.age <= 27 }
println(people.find(canBeInClub27))
null
을 반환한다. find는 firstOrNull과 같다.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의 확장함수
class Book(val title: String, val authors: List<String>)
books.flatMap{ it.author }.toSet()
val strings = listOf("abc", "def")
println(strings.flatMap { it.toList() })
people.map(Person::name).filter{ it.startsWith("A") } // 비효율
people.asSequence() //원본 컬렉션을 시퀀스로 변환한다.
.map(Person::name) // 시퀀스도 컬렉션과 똑같은 API를 제공한다
.filter{ it.startsWith("A") }
.toList() // 결과 시퀀스를 다시 리스트로 변환한다
// 중간 결과를 저장하는 컬렉션이 생기지 않기 때문에 원소가 많은 경우 성능이 눈에 띄게 좋아진다.
Sequence
인터페이스에서 시작한다. 이 인터페이스는 단지 한 번에 하나씩 열거될 수 있는 원소의 시퀀스를 표현할 뿐이다. Sequence 안에는 iterator라는 단 하나의 메서드가 있다. 그 메서드를 통해 시퀀스로부터 원소 값을 얻을 수 있다.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 인스턴스를 만든다
}