이 시리즈를 통해 조재용, 우명인님이 지은 “코틀린으로 배우는 함수형 프로그래밍"을 읽으며 개인적으로 중요하다고 생각하는 각 장(1~11장)의 내용을 정리하고자 함(나중에 내가 빠르게 리마인드 하기 위함임)

if … 완전한 내용의 책은 http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&ejkGb=KOR&barcode=9788966262557 에서 구매가 가능함

함수형 프로그래밍의 특징

불변성, 참조투명성, 일급함수, lazy evaluation

  • 함수를 통해 데이터의 참조 투명성을 보장, 가변 데이터 생성 회피
  • 객체지향 vs 함수형 프로그래밍이 아닌, 명령형 vs 함수형으로 봐야함
  • 사이드 이펙트가 없는 구조를 통해 멀티스레딩 환경에 적함
  • 간결한 코드

 

순수한 함수란 무엇인가?

  • 동일한 입력은 항상 동일한 결과를 출력
    • 같은 파라메터를 전달하더라도 다른 값을 출력하는 함수는 당연히 ‘순수하지 않은 함수’
  • 사이드이펙트가 없음
    • 함수 내부에서 외부의 변수를 바꾸는 등의 행동을 하지 않음
  • 순수한 함수의 효과와 고려사항
    • 멀티스레딩 환경에서 공유 자원 변경에 대한 리스크가 없으므로 안전한 프로그래밍 가능(참조 투명성 만족)
    • 순수하지 못한 함수는 엔터프라이즈급 프로젝트에서 반드시 필요하기 때문에(네트워크 통신, 파일입출력 등) 이러한 필수 불가결한 기능들은 최소화 및 모듈화하는 방식으로 접근해야 함

사이드이펙트 없는 프로그램 작성하기

  • 객체 생성시 불변한 객체로 만듬
  • 함수에서 객체의 상태를 변경하는 경우 객체를 수정하는 대신, 새로운 객체를 생성하여 리턴
  • 새로운 객체를 생성하였을때, 해당 객체의 참조 관계가 있거나 사이드이펙트를 가져가야 하는 작업은 반드시 순수한 영역과 분리하며, 이러한 처리가 외부로 드러나지 않도록 설계해야 함

 

참조 투명성이란?

  • 프로그램의 변경 없이 어떤 표현식을 값으로 대체 가능함
    • ex) 표현식 1 + 1는 2로 써도 같음
    • 투명한 함수 f(x)가 y를 반환한다면 f(x)는 y로 대체 될 수 있음
    • 참조 투명성을 만족 할 경우 컴파일러 코드 최적화에 용이하며, lazy evaulation 구현이 가능해짐

 

일급 함수?

  • 일급 객체(first-class object)
    • 객체를 함수의 매개변수로 전달 가능
    • 객체를 함수의 리턴값으로 사용 가능
    • 객체를 변수나 자료구조에 담을 수 있음
  • 일급 함수(first-class function)
    • 함수를 함수의 매개변수로 전달 가능
    • 함수를 함수의 리턴값으로 사용 가능
    • 함수를 변수나 자료구조에 담을 수 있음

 

일급 함수를 통한 추상화와 재사용성 높이기

  • 일급 함수를 사용하면 명령형 혹은 객체지향 프로그래밍에서 할 수 없는 추상화가 가능하며 재사용성이 높아짐

  • 명령형 vs 객체지향 vs 함수형 프로그래밍의 계산기

    • 명령형 : 계산 기능간의 결합도가 높고, 응집도 / 확장성 / 재사용성이 낮음

      fun main() {
          val calculator = SimpleCalculator()
      
          println(calculator.calculate('+', 3, 1))    // 4
        println(calculator.calculate('-', 3, 1))    // 2
      }
      
      class SimpleCalculator {
        fun calculate(operator: Char, num1: Int, num2: Int): Int = when (operator) {
              '+' -> num1 + num2
              '-' -> num1 - num2
              else -> throw IllegalArgumentException()
          }
      }
      
    • 객체지향 : 캡슐화, 인터페이스를 통한 확장성, 코드 재사용성, 테스트 용이성의 특징을 갖음

      fun main() {
          val plusCalculator = OopCalculator(Plus())
        println(plusCalculator.calculate(3, 1))  // 4
      
          val minusCalculator = OopCalculator(Minus())
          println(minusCalculator.calculate(3, 1))  // 2
      }
      
      interface Calculator {
          fun calculate(num1: Int, num2: Int): Int
      }
      
      class Plus : Calculator {
          override fun calculate(num1: Int, num2: Int): Int {
              return num1 + num2
          }
      }
      
      class Minus : Calculator {
          override fun calculate(num1: Int, num2: Int): Int {
              return num1 - num2
          }
      }
      
      class OopCalculator(private val calculator: Calculator) {
          fun calculate(num1: Int, num2: Int): Int = calculator.calculate(num1, num2)
      }
      
    • 함수형

      fun main() {
          val fpCalculator = FpCalculator()
      
          println(fpCalculator.calculate({ n1, n2 -> n1 + n2 }, 3, 1))    // 4
          println(fpCalculator.calculate({ n1, n2 -> n1 - n2 }, 3, 1))    // 2
      }
      
      class FpCalculator {
          fun calculate(calculator: (Int, Int) -> Int, num1: Int, num2: Int): Int = calculator(num1, num2)
      }
      
      • 일급 함수를 사용해 계산 로직 추상화
      • 인터페이스X, 구현 클래스X
      • 코드 간결성, 유지보수 용이
      • 나눗셈 기능이 필요 할 경우 신규 클래스 추가 없이 기능 추가가 가능함
      • 고차 함수, 펑터, 모나드 등을 활용하면 훨씬 더 강력한 추상화 구현이 가능

 

lazy evaulation을 통한 무한 자료구조

  • 일반적인 프로그래밍 언어에서 코드가 실행될 때 값은 즉시 평가되지만, 함수형 언어는 기본적으로 값이 필요한 시점에 연산이 이루어지며 평가 시점을 지정 할 수도 있음

  • 값이 실제로 필요한 시점까지 연기되기 때문에 시간이 오래 걸리는 작업을 효율적으로 동작시킬 수 있음

  • 명령형 언어에서는 일반적으로 무한대 값을 자료구조에 담을 수 없으나, 함수형 언어에서는 가능함

fun main() {
    val infiniteValue = generateSequence(0) { it + 5 }
    infiniteValue.take(5).forEach { print("$it ") }   // 0 5 10 15 20
}