책/코틀린 쿡북

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 백엔드에서만 꼬리 재귀가 지원된다.