프로그래밍 패러다임은 프로그래머에게 프로그램을 어떻게 바라볼지에 대한 관점을 제공합니다.
명령형 프로그래밍
프로그래밍의 상태와 상태를 변경시키는 구문의 관점으로 접근하는 프로그래밍 방식.
명령형 프로그래밍은 컴퓨터가 실행할 명령들을 실행 순서대로 구현해야 한다.
대부분의 객체 지향 프로그래밍 언어가 명령형 프로그래밍 언어이다. 알고리즘 처리 작업에 적합한 언어이다.
절차지향 프로그래밍, 객체지향 프로그래밍이 이에 속한다.
예시) point를 얻기 위해 실행할 명령들을 순서대로 구현
fun getPoint(customer: Customer): Int {
for (i in 0..customers.size) {
val c = customers[i]
if (customer == c) {
return c.point
}
}
return NO_DATA
}
선언형 프로그래밍
선언으로만 프로그램을 동작시키는 것을 의미한다.
프로그램을 실행하기 위해 구체적인 작동 순서를 나열하지 않아도 된다.
완전하지 않지만 함수형 프로그래밍을 활용해 일정 수준의 선언형 프로그래밍을 할 수 있다.
함수형 프로그래밍은 선언형 프로그래밍의 한 종류로 볼 수 있다.
예시) 구체적인 로직을 직접 작성하지 않고 ~게 할 것이라고 선언
fun getPoint(customer: Customer): Int {
if (isRegisteredCustomer(customer)) {
return findCustomer(customer).point
}
return NO_DATA
}
함수형 프로그래밍
자료 처리를 수학적 함수의 계산으로 취급하는 프로그래밍 패러다임
함수형 프로그래밍의 특징
구체적인 작업 방식은 라이브러리가 결정하며, 어떻게(How)보다는 무엇(What)을 수행할 것인지에 집중한다.
함수형 프로그래밍에는 아래와 같은 특징이 존재한다.
- Side-Effect가 발생하지 않음
- 순수 함수
- 변경 불가능한(불변) 값을 활용
- 1급 객체
- 참조 투명성
부수 효과(Side-Effect)
부수 효과(Side Effect)란 아래와 같은 변화가 발생하는 작업을 의미한다.
- 콘솔 또는 파일 I/O
- 예외가 발생하여 프로그램 실행 중단
- 데이터가 변경됨
val items = mutableListOf("Apple", "Banana", "Orange")
fun add(item: String) {
items.add(item)
}
위 코드에서 add() 함수를 호출하면서 items의 요소가 변경되었다.
따라서 부수 효과가 발생한다고 할 수 있다.
순수 함수(Pure Function)
순수 함수(Pure Function)란 부수 효과들이 발생하지 않는 함수라고 할 수 있다.
순수 함수는 오직 입력으로 들어오는 파라미터에만 의존한다.
함수 자체가 독립적이기 때문에 Side-Effect가 발생하지 않는다.
Thread safe를 보장할 수 있기 때문에 synchronized 등과 같은 별도의 동기화 없이 진행할 수 있다. (병렬성, 동시성 문제 해결)
// Not pure function
val name = "BuNa"
fun printHello() {
println("Hello, $BuNa")
}
// pure function
fun printHello(name: String) {
println("Hello, $name")
}
fun add(a: Int, b: Int): Int = a + b
위 코드에서 첫 번째 함수는 외부의 name에 의존하기 때문에 올바르지 않은 순수 함수라고 할 수 있다.
반면에, 두 번째 함수는 외부의 name이 아닌, 오직 입력으로 들어오는 파라미터에만 의존하므로 순수 함수라고 할 수 있다.
add() 함수 또한 a와 b에만 의존하고 있다.
a와 b를 변경하는 것이 아니라 둘을 합한 결과를 반환하므로 Side-Effect가 발생하지 않는 순수 함수라고 할 수 있다.
불변
불변은 값이 변하지 않기 때문에 시간적 결합(Temporal Coupling)을 고려하지 않아도 된다는 장점을 가지고 있다.
시간적 결합이란 시간이 지남에 따라 어떤 데이터가 변할 수 있음을 의미한다.
또한, 값이 변하지 않으므로 멀티 쓰레드 환경에서 Data Inconsistency 문제를 고려하지 않아도 된다. (병렬성, 동시성 문제 해결)
따라서 불변은 값을 예측 가능하게 해주며, 신뢰를 보장한다.
1급 객체
함수형 프로그래밍에서는 함수가 일급 객체(first-class citizen)의 역할을 한다.
함수를 일급 객체로 활용이 가능할 경우, 함수를 변수에 할당하거나, 인자로 받거나, 함수의 반환 값으로 활용하는 것이 가능하다.
// 함수를 인자로 전달
fun printName(getName: () -> String)) {
println(getName())
}
printName {
"BuNa"
}
여기서 람다(Lambda)와 고차 함수(Higher order function) 개념이 사용된다.
람다는 익명 함수의 다른 표현이다. 즉, 함수는 함수인데 이름이 없는 경우를 의미한다. (getName이라는 익명 함수를 사용)
고차 함수는 함수에서 다른 함수를 인자로 전달받는 함수를 의미한다. (printName이 getName 함수를 전달받음)
참조 투명성(Referential Transparency)
- 동일한 인자에 대해 항상 동일한 결과를 반환한다.
- 기존 값은 변경되지 않고 유지된다.
함수형 프로그래밍에서는 Side-Effect가 발생하지 않아야 한다.
참조가 투명하다는 것은 동일한 인자에 대해 함수를 실행했을 때, 어떠한 상태 변화 없이 항상 동일한 결과를 반환하여 결과를 (투명하게) 예측할 수 있다는 의미이다.
// 올바르지 않은 참조 투명성 사례
data class Cat(var age: Int) {
fun addAge(value: Int): Int {
age += value
return age
}
}
// 올바른 참조 투명성 사례
data class Cat(val age: Int) {
fun addAge(value: Int): Cat {
return Cat(age + value)
}
}
첫 번째 코드는 addAge() 호출 시 Cat의 상태를 변화시킨다.
다시 말해 기존 값이 변경될뿐더러, addAge() 메서드의 인자로 같은 값을 전달해도 동일한 결과를 반환하지 않는다.
반면 두 번째 코드는 addAge() 호출 시 인자로 받은 나이를 더하여 새롭게 Cat 객체를 반환하고 있다.
결국 기존 Cat 객체를 변화시키지도 않고, 동일한 인자에 대해 매번 동일한 결과를 반환한다.
결론
정리하자면, 함수형 프로그래밍은 1급 객체의 특징을 가지고 있으며, 부수 효과가 없는 순수 함수를 사용하여 참조 투명성을 지킬 수 있다.
(결국 이 특징들은 서로 뗄 수 없는 관계를 가지고 있다.)
함수형과 명령형은 다른 프로그래밍 패러다임이지만 둘을 혼용할 수 있다.
따라서 프로그래밍의 요구사항과 상황에 따라 적절한 것을 선택할 필요가 있다.
추가로, 함수형 프로그래밍을 연습하기 위해 아래와 같은 방법을 택할 수 있다.
- 프로그래밍의 기본 틀은 객체 지향 프로그래밍 지향
- 함수 내부 구현은 함수형 프로그래밍을 지향
- 객체의 상태 관리는 불변을 지향한다.
'대외활동 > 우아한테크코스' 카테고리의 다른 글
[우아한테크코스] 📚 레벨로그 - 레벨1 인터뷰 (0) | 2023.03.30 |
---|---|
[학습로그] 최신 Android에서는 왜 MVC 패턴을 사용하지 않는가? (1) | 2023.03.27 |
[우아한테크코스] 우아한테크코스 한 달 생활기 (2) | 2023.03.05 |
[우아한테크코스] 원시값 포장과 일급컬렉션은 무엇이고, 어디까지 감싸야 할까? (1) | 2023.02.27 |
[우아한테크코스] 2주차 - 점진적 리팩터링(Incremental Refactoring) (5) | 2023.02.24 |