코틀린으로 배우는 함수형 프로그래밍

《코틀린으로 배우는 함수형 프로그래밍》은 함수형 프로그래밍을 멀티 패러다임 언어인 코틀린으로 다룹니다.

코틀린은 자바와 유사해 공부하기도 쉽고, 문법도 간단합니다. 또한 하이브리드형 언어라 함수적 특징을 가지고 있으면서, 객체지향 프로그래밍도 가능하도록 설계되어 있습니다. 하나의 언어로 명령형, 객체지향형, 함수형 예제를 모두 설명할 수 있지요. 순수 함수형 언어를 실전에서 쓰는 경우가 드문 점을 감안하면 실질적으로 많은 도움이 될 것입니다.

이 책에서는 언어가 제공하는 함수적인 기능을 단순히 사용해 보는 것을 넘어 고차 함수, 함수적 자료구조, 타입 등을 직접 만들어 봅니다. 따라서 차근차근 잘 따라간다면 함수형 라이브러리를 만들어서 제공할 수 있는 수준까지 전진할 수 있습니다.

함수형이어야 하는 이유

함수형 프로그래밍은 이미 많은 곳에서 쓰이고 있습니다. 새로 나오는 언어들은 대부분 함수형 프로그래밍을 지원하고 있으며, 많은 프로그램, 라이브러리 들이 함수형을 지원하고 있습니다.

함수형 프로그래밍은 간결하고, 안전하고, 효율적입니다. 좋다는 이야기는 많이 들었지만, 실제로 어떤 식으로 장점이 구현되는지는 체감하기 어려울 수 있습니다. 개발자니까, 코드로 살펴보는 게 좋겠습니다.

간결성

함수형 프로그래밍은 명령형 프로그래밍에 비해 코드가 간결합니다. 다음은 5보다 큰 값만 제곱한 리스트를 반환하는 함수를 명령형과 함수형으로 구현한 코드입니다.

명령형
fun example1(n: Int): List<Int> {
 val result: MutableList<Int> =
mutableListOf()
 for (i in 1..n) {
  if(i > 5){
     val squareValue = i * i
     result.add(squareValue)
   }
 }
 return result
}
함수형
fun example2(n: Int): List<Int> =
  (1..n)
    .filter { it > 5 }
    .map { it * it }

한눈에 봐도 함수형이 훨씬 간결하다는 걸 알아차릴 수 있습니다.

안전성

함수형 프로그래밍은 부수효과 없는 안전한 프로그램을 만들 수 있게 합니다. 다음은 StringBuilder를 입력으로 받아서 문자열에 value를 붙여주는 함수를 구현한 코드입니다.

명령형
fun appendPostfix(strBuilder: StringBuilder, value: String): StringBuilder {
  strBuilder.append(value)
  return strBuilder
}

fun main() {
  val source = StringBuilder("Hello")

  println(appendPostfix(source, ", Kotlin"))// Hello, Kotlin
  println(appendPostfix(source, ", FP"))    // Hello, Kotlin, FP
}

appendPostFix 함수를 처음 호출했을 때 source를 변경했기 때문에, 두 번째 호출에서 의도하지 않는 결과가 나왔습니다.

제대로 동작하게 하려면 appendPostFix 함수를 주의해서 사용하거나 함수 내부에서 새로운 StringBuilder를 생성해 원본 빌더를 변경하지 않게 하는 등 예외처리를 해야 합니다.

불변 객체를 사용하면 이런 예외를 처리하지 않고도 간결하게 코드를 작성할 수 있습니다.

함수형
fun appendPostfix(str: String, value: String): String {
return str + value
}

fun main() {
val source = "Hello"

println(appendPostfix(source, ", Kotlin")) // Hello, Kotlin
println(appendPostfix(source, ", FP")) // Hello, FP

}


효율성

함수형 프로그래밍은 효율적입니다. 이 장점은 잘 와닿지 않을 수도 있습니다. 위에서 살펴본 example1example2의 코드만 비교해 봐도, 함수형 코드인 exmaple2의 코드가 훨씬 비효율적이기 때문입니다.

다음은 그 결과를 확인해 보기 위한 코드입니다.

비효율적인 함수형 코드결과
fun example3(n: Int): List<Int> =
  (1..n)
      .filter {
        println("over5 $it")
        it > 5
      }
      .map {
        println("square ${square(it)}")
        it * it
      }

fun main() {
  example3(10)
}
over5 1
over5 2
over5 3
over5 4
over5 5
over5 6
over5 7
over5 8
over5 9
over5 10
square 36
square 49
square 64
square 81
square 100

선언적으로 작성한 코드가 리스트의 모든 값에 대해서 대상 로직을 수행하기 때문입니다. 이러한 문제를 해결하기 위해 함수형에서는 값이 필요한 시점에 평가되는 게으른 컬렉션을 제공합니다.

효율적인 함수형 코드결과
fun example4(n: Int): Sequence<Int> =
  (1..n)
  .asSequence()
    .filter {
      println("over5 $it")
      over5(it)
    }
    .map {
      println("square ${square(it)}")
      square(it)
    }

fun main() {
  example4(10).first()
}
over5 1
over5 2
over5 3
over5 4
over5 5
over5 6
square 36








이제는 함수형으로 프로그래밍할 때라는 거! 알고 있지만 여러 장벽 때문에 시작하지 못했다면 《코틀린으로 배우는 함수형 프로그래밍》과 함께해 보세요.

yes24 / 교보문고 / 알라딘 / 인터파크

본문 예제와 연습문제 / 정오표