- 함수형 프로그래밍은 애플리케이션, 함수의 구성요소, 더 나아가서 언어 자체를 함수처럼 여기도록 만들고, 이러한 함수 개념을 가장 우선순위에 놓는다.
- 함수형 사고방식은 문제의 해결 방법을 동사(함수)들로 구성(조합)하는 것
- 마이클 포거스 [클로저 프로그래밍의 즐거움]
- 성공적인 프로그래밍을 위해 부수 효과를 미워하고 조합성을 강조하는 프로그래밍 패러다임
- 순수함수를 만든다.
- 모듈화 수준을 높인다.
- Avoids changing State and Mutable data
- 상태와 Data 변경하는 것을 피하면서 프로그래밍
- Functional Programming is programming without assignment satements
- OOP 와 반대되는 개념은 아니다. 오히려 OOP를 잘 알아야 함수형 프로그래밍도 잘 할 수 있다.
// 데이터(객체) 기준
dog.moveLeft();
cat.moveLeft();
dog.moveRight();
// 함수 기준
moveLeft(dog);
moveRight(cat);
moveLeft({x:5, y:2});
- 객체지향 프로그래밍
- 데이터를 먼저 만들고 함수 구성
- 함수형 프로그래밍
- 함수를 만들고 그에 맞는 데이터 셋을 구성
- 불변 객체를 사용한다.
- 참조 투명성을 가진다.
- 실제로 필요해지는 경우에 연산을 시작한다.
- 코드를 간결하게 해준다.
- 에러가 없기 때문에 병렬처리하기 좋다.
- 반대 : Mutable
java
에서final
과 같음.
val immutableList : List<Int> = listOf(1,2,3)
immutableList.add(10) // error
immutableList.plus(10) // not error
println(immutableList) // [1,2,3]
println(immutableList.plus(10) // [1,2,3,10]
var newList : immutableList.plus(10)
println(innutableList) // [1,2,3]
println(newList) // [1,2,3,10]
- 자료구조 수정을 할때 원본은 변화지 않고 수정을 통해 새로운 객체를 반환한다.
- 값이 변하지 않기 때문에 내가 몇 번을 참조해도 값이 변하지 않는다.
- 참조 투명성
fun main(args: Array<String>) {
println(pureFunction("FP") // Hello FP
println(pureFunction("FP") // Hello FP
println(pureFunction("FP") // Hello FP
}
fun pureFunction (iniput: String): String {
return "Hello " + input
}
- 언제 몇 번을 호출하든 input이 같다면 output이 같다.
- 0개 이상의 인수를 가지고, 한 개이 상의 결과를 반환하며 부수효과가 없다.
- no side effect
- 자료구조를 고치거나 필드값에 할당
- 자료구조를 제자리에서 수정함
- 예외 발생, 오류로 실행 중단
- 파일에 쓰기 등의 I/O 동작 수행
- no side effect
fun main(args: Array<String>) {
println(nonPureFunction("FP") // Hello FP
println(nonPureFunction("FP")
println(nonPureFunction("FP")
}
val strBuilder: StringBuilder = StringBuilder("Hello ")
fun nonPureFunction(input: String): String {
return strBuilder.append(input).toString()
}
- 출력값이 여러번 호출하면 달라진다.
- 동시성을 생각했을 때 (멀티스레드) 굉장히 프로그래밍이 복잡해진다. → 에러를 정확히 확인 못할 수도 있다.
- 테스트 코드를 작성하기 힘들다.
var x =1
fun nonPureFunction2(input: Int): Int{
return input+x
}
- 외부 변수를 참조한다면 순수 함수가 아니다.
- side effect가 있으므로
- 변수가 언제 바뀔지 모르기 때문에 순수함수가 아니다.
val lazyValue2: () -> Unit = {
println("FP")
}
lazyValue2 //
lazyValue2 //
lazyValue2 //
lazyValue2() // FP
- → allow
- 왼편 == input
- 오른편 == output
- 불변 자료구조
- 순수함수 자료구조
- 자료 공유
fun main(args: Array<String>) {
val value = 3
val result = plusThree(value)
println(result) // 6
println(value) // 3
}
fun plusThree(x: Int): Int{
return x+3
}
fun main(args: Array<String>) {
val list = listOf(1,2)
val listResult = plusThree(list)
println(list) // 1,2
println(listResult) // 1,2,3
}
fun plusThree(list: List<Int>): List<Int>{
return list.plus(3)
}
- 이렇게 매번 변수가 바뀌면 비효율적이지는 않을까?
- 자료 공유 Data Share
- 원본 리스트를 반환을 하지만, 추가로 생성되는 것보다는 재활용하고 있다.
- 이를 자료공유라고 한다.
- 어떻게 보다 무엇에 집중
- 종료조건부터 생각하자
- 재귀를 반복할 수록 종료 조건으로 수렴하도록 기획
fun factorial (num: Int): Int {
return num*factorial(num-1)
}
- 생각보다 효율적인 코드는 아니다.
- Performance - SOF (스택오버플로우 생길 수 있다)
// TailRecursion
// 스택이 쌓이지 않고 라인대로 동작한다.
tailrec fun factorial3(num: Int, acc: Int=1): Int =
when (num) {
1 -> acc
else -> factorial3(num - 1, acc*num)
}
- TailRecursion 꼬리재귀 를 통해 함수형 프로그래밍으로 실행 가능하다.
- 이렇게 하면 가독성 + 성능 + 스택오버플로우 발생하지 않는다.
- 상황에 따라 선택하면 된다.
- 좋아지는 하드웨어 성능
- 좋아지는 컴파일러
- 함수형 프로그래밍 기술의 발전
- 좋아지는 분산, 리액티브 환경
- 동시성 + 병렬성 관련 기술
- 성공적인 적용 사례와 영향
Java 8부터 Java에서도 함수형 프로그래밍이 가능해졌다.
- stream api
- lamda
- 함수형 인터페이스