책/코틀린 쿡북
4장 - 함수형 프로그래밍
ballde
2022. 9. 27. 14:53
https://balldev.tistory.com/18
함수형 프로그래밍 & 람다 & 메소드 참조
함수형 프로그래밍 일단 함수형 프로그래밍의 등장 배경을 보겠습니다. 일단 명령형 프로그래밍 기반에서 소프트웨어의 크기가 커졌습니다. 그래서 스파게티 코드를 유지 보수할 수가 없어졌
balldev.tistory.com
4.1 알고리즘에서 fold 사용하기
- fold 함수를 사용해 시퀸스나 컬렉션을 하나의 값으로 축약시킨다.
inline fun <R> Iterable<T> fold(
initial : R,
operation: (acc:R, T) -> R
): R
첫번째 인자: 누적자의 초기값
두번째 인자(람다): 두개의 인자를 받아 누적자를 위해 새로운 값을 리턴
예시
// 합
fun sum(vararg nums: Int) = nums.fold(0) {acc, n -> acc + n }
val numbers = intArrayOf(3, 1, 4, 1, 5, 9)
sum(*numbers)
// 팩토리얼
fun recursiveFactorial(n: Long): BigInteger =
when (n) {
0L, 1L -> BigInteger.ONE
else -> BigInteger.valueOf(n) * recursiveFactorial(n - 1)
}
fun factorialFold(n: Long): BigInteger =
when (n) {
0L, 1L -> BigInteger.ONE
else -> (2..n).fold(BigInteger.ONE) {
acc, i -> acc * BigInteger.valueOf(i)
}
}
// 피보나치
fun fiboFold(n: Int) = (2 until n).fold(1 to 1) {
(prev, curr), _ -> curr to (prev + curr)
}.second
4.2 reduce를 사용해 축약하기
- 비어있지 않는 컬렉션의 값을 축약하고 싶지만 누적자의 초기값을 설정하고 싶지 않다.
- reduce는 fold 함수랑 거의 같은데 사용 목적도 같다.
inline fun <S, T : S> Iterable<T>.reduce(
operation: (acc: S, T) -> S
): S
- 비어있는 컬렉션은 예외를 발생시킨다.
- 누적자는 컬렉션의 첫번째 원소로 초기화
fun sumReduce(vararg nums: Int) = nums.reduce {acc, i -> acc + i}
fun sumReduce(vararg nums: Int) = nums.reduce {acc, i -> acc + i * 2}
// 모두 2배씩 더하고 싶지만 위의 함수는 첫번째 인자가 2배가 되지 않음
- java 스트림에는 reduce라는 이름의 중복된 메서드 2개가 있음
- 바이너리 연산자를 받음 (초기값을 받지 않는 리턴타입은 Optional 이다.)
- 바이너리 연산자 + 초기값
4.3 꼬리 재귀 적용하기
- 재귀 프로세스를 실행하는 데 필요한 메모리를 최소화하고 싶다.
- 꼬리 재귀를 사용해 프로세스 알고리즘을 표현하고 해당 함수에 tailrec 키워드를 추가
fun recursiveFactorial(n: Long): BigInteger =
when (n) {
0L, 1L -> BigInteger.ONE
else -> BigInteger.valueOf(n) * recursiveFactorial(n - 1)
}
- 너무 빠르게 크기가 커짐 (BigInteger 반환)
- 너무 큰 숫자는 StackOverflowError
@JvmOverloads
tailrec fun factorial(n: Long, acc: BigInteger = BigInteger.ONE): BigInteger =
when (n) {
0L -> BigInteger.ONE
1L -> acc
else -> BigInteger.valueOf(n) * recursiveFactorial(n - 1)
}
- 콜 스택에 새 스택 프레임을 추가하지 않게 구현하는 특별한 종류의 재귀방법
- 꼬리 재귀의 구현을 위해 재귀 호출이 연산의 마지막에 수행 → 스택 프레임을 재사용 가능
- tailrec 키워드가 핵심 (없으면 컴파일러가 최적화해야하는지 알 수가 없음)
tailrec 변경자를 적용할 수 있는 함수의 자격 요건
- 해당 함수는 반드시 수행하는 마지막 연산으로서 자신을 호출해야한다.
- try catch finally 블록안에서는 tailrec을 사용할 수 없다.
- JVM 백엔드에서만 꼬리 재귀가 지원된다.