2023 01 11 코틀린 공부 - 람다 프로그래밍
5장 람다로 프로그래밍
함수형 언어에서는 함수를 직접 다른 함수에 전달할 수 있다. 코틀린은 람다식을 이용해서 이를 구현한다.
람다 : 다른 함수에 넘길 수 있는 작은 코드 조각 주로 컬렉션 처리에 사용한다.
컬렉션의 반복문을 쓰는 대신 람다식을 이용하여 간단하게 최대값등을 구할 수 있다.
val people = listOf(Person("Alice",29),Person("Bob",31))
println(people.maxByOrNull { p: Person -> p.age }!!.name)
람다식은 ->기준으로 왼쪽이 파라미터, 오른쪽이 람다 본문이다.
val mis = {x:Int,y:Int->x-y}
println(mis(3,4))
람다의 파라미터가 하나뿐이고 타입 추론이 가능한 경우 자동 생성된 파라미터 it를 바로 쓸 수 있다.
println(people.maxByOrNull { it.age }!!.name)
또한 확장 함수도 똑같이 만들 수 있다.
fun Person.isAdult() = age>=20
val predicate = Person::isAdult
println(predicate(people[0]))
컬렉션에서 원하는 값만 취하는 filter함수나 원소들을 변환하는 map함수에서도 람다식을 사용할 수 있다.
val list = listOf(1,2,3,4,5,6)
println(list.filter { it%3==0 })
println(list.map { it*4 })
list 뿐 아니라 map에도 람다를 이용할 수 있다.
val numbers = mapOf(0 to "zero", 1 to "one")
println(numbers.mapValues { it.value.uppercase() })
컬렉션에는 조건을 모두 만족하는지 확인하는 all, 하나라도 만족하는지 확인하는 any, 만족하는 개수를 세는 count가 있으며 이들 모두 람다식을 사용할 수 있다. 하나를 찾고 싶으면 find를 사용하면 된다. 특정 값으로 구분할 수 있는 groupBy에도 쓸 수 있다.
val canBeInClub27 = {p:Person->p.age<=27}
println(people.all{canBeInClub27(it)})
println(people.any { canBeInClub27(it) })
println(people.count{canBeInClub27(it)})
println(people.find{canBeInClub27(it)}!!.name)
println(people.groupBy { it.age })
flatMap은 람다를 적용한 결과 얻어지는 리스트들을 하나의 리스트로 모은다
val strings = listOf("aka","ckc")
println(strings.flatMap { it.toList() })
컬렉션을 map, filter를 통해 즉시 생성할 경우 중간 결과를 새 컬렉션에 임시로 담는다. 이 경우 컬렉션의 원소가 많아지면 효율이 떨어지는데 효율적으로 하기 위해 각 연산이 컬렉션을 직접 사용하는 대신 시퀀스를 사용하게 만들어야 한다.
이러한 지연 계산 시퀀스는 Sequence 인터페이스로부터 시작한다. 장점은 시퀀스의 원소가 필요할 때 비로소 계산되기 때문에 결과를 저장하지 않고 연쇄적으로 연산을 적용해서 효율적으로 계산을 수행할 수 있다. 차례대로 원소를 이터레이션하는것이 아닌 원소를 인덱스를 사용해 접근하는 등 다른 API 메서드가 필요하면 리스트등으로 변환해야 한다.
people.asSequence()
.map(Person::name)
.filter{it.startsWith('A')}
.toList()
시퀀스의 연산은 시퀀스를 반환하는 중간 연산과 컬렉션, 원소, 숫자 또는 객체를 반환하는 최종 연산으로 나뉜다.
시퀀스는 중간처리 결과를 연쇄적으로 처리하지만 toList(),first()같은 최종 연산이 없다면 아무 내용도 출력되지 않는다.
또한 연산순서는 시퀀스를 사용하지 않으면 모든 원소 map-> 모든 원소filter 의 순서이지만 시퀀스를 사용하면 각 원소에 대해 순차적으로 map, filter가 적용된다. 따라서 아래의 코드의 출력은 map(1) filter(1) map(2) filter(4) 4이 된다.
println(listOf(1,2,3,4,).asSequence()
.map{print("map($it) "); it*it}
.filter{print("filter($it) "); it%2==0}
.first())
컬렉션에 asSequence()를 호출하는것 말고도 generateSequence함수를 사용해서 시퀀스를 만들 수 있다.
val naturalNumbers = generateSequence(0){it+1}
val numbersTo50 = naturalNumbers.takeWhile { it<=10 }
println(numbersTo50.sum())
SAM 생성자는 람다를 함수형 인터페이스의 인스턴스로 변환할 수 있게 컴파일러가 자동으로 생성한 함수다. SAM 생성자의 이름은 사용하려는 함수형 인터페이스의 이름과 같다.
어떤 객체의 이름을 반복하지 않고 그 객체에 다양한 연산을 할 수 있는 with가 있다. 아래의 코드에서 stringBuilder를 계속 사용하지 않고 람다식 안에서 this를 이용해서 append, toString()을 사용할 수 있다.
fun aplphabet(): String{
val stringBuilder = StringBuilder()
return with(stringBuilder){
for(letter in 'A'..'Z'){
this.append(letter)
}
append("\n Now I know the alphabet!")
this.toString()
}
}
더 간단하게도 만들 수 있다.
fun alphabet() = with(StringBuilder()) {
for (letter in 'A'..'Z') {
append(letter)
}
append("\n Now I know the alphabel!")
this.toString()
}
apply는 with와 비슷하지만 항상 자신에게 전달된 객체를 반환한다. 위의 코드를 변환하면 StringBuilder를 반환하므로 toString()을 람다식 뒤에 붙어주어야 한다.
fun alphabet() = StringBuilder().apply {
for (letter in 'A'..'Z') {
append(letter)
}
append("\n Now I know the alphabel!")
}.toString()
코드 정리는 여기에
https://github.com/posite/kotlin-study
GitHub - posite/kotlin-study
Contribute to posite/kotlin-study development by creating an account on GitHub.
github.com