함수형 프로그래밍 in 코틀린 - 함수형 프로그래밍이란?[1]
이 시리즈를 통해 조재용, 우명인님이 지은 “코틀린으로 배우는 함수형 프로그래밍"을 읽으며 개인적으로 중요하다고 생각하는 각 장(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
}