chapter 5. Programming with lambdas

lambdas: 다른 함수에 넘길 수 있는 작은 코드 조각.

lambda

event가 발생 했을 때, handler를 통해 event를 처리하도록 하는 행위를 Java에서는 anonymous inner class로 처리했다.
이런 inner class는 verbose syntax 갖게 된다.

  • listener와 같은 예 들이 있다. (꼴 보기 싫지)

Java 8 에서는 이런 문제를 lambda로 해결할 수 있도록 했다.
Kotlin은 Java 6을 베이스로 했기 때문에 Java와는 다른 lambda를 지원한다.

lambda delegates

val people = listOf(Person("A", 29), Person("B", 31))
people.maxBy{it.age}

If a lambda just delegates to a method or property, it can be replaced by a member.

  • 영작이 어렵다. 값을 주기만 하는 경우, 아래와 같이 수정이 가능하다.
  • 이건 뭐 Java 8에서도 유사하게 쓰이니까.
people.maxBy(Person::age)

lambda syntax

lambda syntax

kotlin의 lambda는 대괄호 안에 있는 특성이 있다.

  • 이 부분은 java와 달라서 낯설긴 하다.

syntax sugar

people.maxBy({p: Person -> p.age}) // 기본 lambda syntax
                                   // syntax shortcut이 없어 장황함
people.maxBy() {p: Person -> p.age} // Kotlin에서는 last argument의 lambda를 밖으로 빼낼 수 있음
people.maxBy {p: Person -> p.age} // lambda가 only argument면 괄호를 지울 수 있음

위 방식들은 가독성에만 차이가 있을 뿐 모두 같다.
lambda의 경우 마지막으로 빼서 저렇게 만드는 것이 가독성에 더 좋다.

type inferred sugar

people.maxBy {p -> p.age} // 얘는 type을 명시하지 않은 inferred.
people.maxBy {it.age} // autogenerated argument name.

type inferred lambda 쓰기 위해서는, type 추론이 가능해야 하기 때문에 lambda variable 만들어지는 경우에는 사용할  없다.  
```kotlin
val getAge = {p: Person -> p.age} // lambda가 value로 만들어짐
                                  // 따라서 type inferred lambda 사용 불가
people.maxBy(getAge)

accessing variable scope

Java와는 다르게 final value가 아닌 애들한테도 접근 및 수정이 가능하다.
이렇게 lambda에서 access하는 외부 변수들을 captured 되었다고 한다.

member reference

function을 value로 변환하는 것.

// 함수 변환
val getAge = {person: Person -> person.age}
people.maxBy(Person::age)

// 생성자 변환
data class Person(val name: String, val age: Int)
val createPerson = ::Person
val p = createPerson("Alice", 29)

collection library

Kotlin은 Java 보다 collection에 대해 더 많은 library를 제공한다.
대부분 명확한 naming을 갖는다.

filter & map

filter는 말 그대로 filtering 하는거다. java 에서와 유사하다.
map은 각 value들을 maping 해주는 건데, 이것도 java와 유사하다.
예제를 보면 간단하다.

val people = listOf(Person("Alice", 29), Person("Bob", 31))
people.filter { it.age > 30 }

val list = listOf(1, 2, 3, 4)
list.map { it * it }

상황에 따라 다르지만, filter와 map을 연달아서 사용한다면 filtering 한 후에 mapping을 하는 것이, mapping 하는 element 수를 줄여 성능에 더 도움이 되는 편이다.

all, any

명확한 function name이다.
allany의 경우 !allany와 같다는 것. !any 역시도 그렇다는 것을 잘 기억하고 가독성이 좋은 방향(!를 제거하는)으로 수정해가는 것이 좋다.

count

count의 경우 size와 비교해볼 필요가 있다.
size는 collection에 대한 기본 함수니까.

count를 써야할 때는 어떤 때일까?

val canBeInClub27 = { p: Person -> p.age <= 27 }

people.count(canBeInClub27)
people.filter(canBeInClub27).size

위와 같은 상황에서 명확하다.
count의 경우 각각의 element를 추적하며 조건에 맞는 수를 세지만,
size를 위해서 filter 조건에 맞는 collection을 생성하고 그 수를 반환한다.
따라서 filtering 이 필요한 경우에서는 count를 쓰는게 맞다.

find

find는 만족하는 element가 있다면 첫 번째로 매칭하는 element를 반환하고, 없다면 null을 반환한다.

etc

그 외에도 많은 기본 library들이 있다. 대부분 명확하고 간단하다.
groupBy: element 기반으로 list를 map으로 만드는
flatMap: 2겹의 collection을 풀어서 1겹으로 만드는

lazy operation

kotlin에서 lambda를 사용할 때 제일 중요한 부분이 아닌가 싶다.
lambda를 사용하면 매번 작업마다 새로운 temporary collection을 생성해내는데, 이건 비효율 적이다. 그래서 마지막에 한 번에 연산을 마무리하도록 lazy operation을 지원한다.

people.map(Person::name).filter { it.startsWith("A") }
people.asSequence().map(Person::name).filter { it.startsWith("A") }.toList() // lazy operation

large collection일 경우 lazy operation를 쓰는게 좋다.

lazy operation

intermediate operation은 another sequence를 return 하고,
terminal operation은 result를 return 한다.

주의사항:
If you’re targeting Java 8, streams give you one big feature that isn’t currently implemented for Kotlin collections and sequences: the ability to run a stream operation (such as map() or filter()) on multiple CPUs in parallel.

다음 코드에서는, 첫 번째 find를 하고 이후의 element는 계산도 하지 않는다.
이런 부분도 lazy 의 장점이라고 할 수 있다.

println(listOf(1, 2, 3, 4).asSequence().map { it * it }.find { it > 3 })

with & apply

with는 parameter로 받은 값의 naming을 여러번 반복해서 쓰지 않아도 되게 해주는 역할을 한다.
받은 parameter의 변수/함수들을 내부 호출하듯 사용하는거지.

fun alphabet() = with(StringBuilder())
    { for (letter in 'A'..'Z') {
        append(letter) // 여기 StringBuilder()의 함수를 그냥 호출
    }
    append("\nNow I know the alphabet!")
    toString() // with()가 반환할 값
}

apply는 with랑 동일한데, 호출한 object(reciever object) 반환한다.

fun alphabet() = StringBuilder().apply
    { for (letter in 'A'..'Z') {
        append(letter)
    }
    append("\nNow I know the alphabet!")
}.toString()