프로그래밍 패러다임과 함수형 프로그래밍
컴퓨터 프로그램의 구현을 개념화 하고 구조화 하는 방법. 하나의 언어는 하나 또는 여러 개의 패러다임을 지원하는 것으로 분류 가능하다. 프로그래머에게 프로그래밍의 관점을 갖게 하고, 코드를 어떻게 작성할지 결정하는 역할을 한다.
최근의 프로그래밍 패러다임은 아래와 같이 분류된다.
- 명령형 프로그래밍: 무엇(What)을 할 것 인지보다 어떻게(How) 할 것인지를 설명하는 방식,
컴퓨터에게 절차와 순서를 하나하나 명령함.- 절차 지향 프로그래밍: 수행되어야 할 순차적인 처리 과정을 포함하는 방식
- 객체 지향 프로그래밍: 객체들의 집합으로 프로그램의 상호작용을 표현
- 선언형 프로그래밍: 어떻게(How) 보다 무엇(What)을 할 건지 설명하는 방식
- 함수형 프로그래밍: 순수 함수를 조합하고 SW를 만드는 방식
함수형 프로그래밍
로버트 C. 마틴은 함수형 프로그래밍을 아래와 같이 정의했다.
대입문이 없는 프로그래밍
위는 철학적 정의에 가깝다.
상태 변경을 최소화하고, 불변셩을 지향하는 프로그래밍
함수형 프로그래밍은 사실 이전부터 존재하던 방법이다. 다만, CPU 속도가 한계에 부딪히고, 멀티 코어 기법을 사용하면서 생긴 문제였던 동시성 문제를 해결하기 위한 대안으로 다시 주목 받게 되었다. 명령형은 여러 스레드가 상태를 공유하고 변경할 때 버그가 발생하기 쉽지만, 함수형은 불변성을 지향하기 때문에 스레드 환경에서 안전하게 동작한다.
대입문이 없다는 말의 예시를 들어보자.
val numbers = listOf(1, 3, 5, 7, 9)
val result = mutableListOf<Int>() // 1. 상태를 저장할 공간 생성
for (number in numbers) { // 2. 어떻게 반복할 건지 명시
if(number > 5) { // 3. 어떻게 검사할 건지 명시
result.add(number) // 4. 상태 변경
}
}
위는 명령형 프로그래밍의 한 방법이다. 아래는 위 코드를 바꾼 방법이다.
val numbers = listOf(1, 3, 5, 7, 9)
val result = numbers.filter { it > 5 } // What: 5보다 큰 것만 선언
함수형은 기존의 numbers의 상태를 변경하지 않고, 조건에 맞는 요소들만 추려낸 새로운 리스트를 반환한다. 개발자는 ‘상태를 어떻게 변경할 것(How)인가’ 보단 ‘어떤 데이터를 원하는가(What)’에만 집중 할 수 있게 된다.
코틀린이 상태 변경이 아닌 어떤 데이터를 원하는 가를 나타내는지 확인해보자. filter함수는 대표적인 함수형 프로그래밍의 예시이다.
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
return filterTo(ArrayList<T>(), predicate)
} // ctrl + 좌클릭을 하면 해당 함수가 먼저 나온다.
public inline fun <T, C : MutableCollection<in T>> Iterable<T>.filterTo(destination: C, predicate: (T) -> Boolean): C {
for (element in this) {
if (predicate(element)) destination.add(element)
}
return destination
}
이렇기 때문에, 해당 .filter함수는 데이터의 상태를 변경하는 게 아닌 원하는 데이터만 내놓게 된다.
순수 함수(Pure Function)
순수 함수의 정의는 아래와 같다.
- 동일한 입력에는 항상 동일한 출력을 반환한다. 입력만이 함수의 출력 결과에 영향을 주는 함수이다.
- 부수 효과(Side-Effect)가 없다.
fun add(num1: Int, num2: Int): Int {
return num1 + num2
}
fun getRandomNumber(): Int {
return (0 until 10).random()
}
참조 투명성(Referential Transparency)
함수 호출 부분을 그 함수의 결과값으로 통째로 치환해도 프로그램의 동작에 전혀 변화가 없는 성질이다.
val result = add(2, 3) + add(2, 3)
// val result = 5 + 5로도 가능하다.
외부에 영향을 주지도, 받지도 않아서 코드를 예측하기 쉽고, 컴파일러는 이 성질을 이용해 캐싱이나 최적화를 쉽게 수행가능 하다.
- 메모리제이션/캐싱
- 참조 투명성이 보장되면 연산이 매우 오래 걸리는 함수의 결과를 캐시에 저장해놓고 재사용 할 수 있따.
- 테스트 용이성(Testability)
- DB나 네트워크, 전역 변수 상태를 세팅(Mocking) 할 필요가 없다. 특정 값을 넣었을 때 기대하는 값이 나오는가? 만 테스트하면 되므로 단위 테스트 작성이 매우 직관적이고 쉬워진다.
- 안전한 리팩토링과 지연 평가(Lazy Evaluation)
- 함수의 실행 결과가 언제나 동일함이 보장되므로, 코드를 이리저리 옮기거나 실행 순서를 뒤바꾸어도(나중에 필요할 때까지 실행을 미뤄도) 시스템이 망가지지 않는다.
부수 효과(Side-Effect)
함수가 자기 할 일(값 리턴) 외에 외부의 상태를 변경하거나, 외부와 상호작용 하는 모든 행위를 말한다.
- print()로 콘솔에 출력하기(I/O 작업)
- 전역 변수(var)의 값을 변경하기
- 데이터베이스에 저장하기
- 화면(UI)을 업데이트하기
- 예외(throw )를 던지기
아래는 부수 효과의 예시이다.
fun increment(num: Int): Int {
print(num)
return num + 1
}
var a = 0
fun add(num1: Int, num2: Int): Int {
var sum = num1 + num2
a = sum
return sum
}
소프트웨어에서 부수 효과를 100% 없애는 것은 불가능하다. 데이터베이스에 데이터를 저장하거나, 화면에 UI를 그리고, 서버와 통신하는 모든 '유의미한 작업'이 결국 부수 효과이기 때문이다.
따라서 현대 프로그래밍(특히 애플리케이션 개발)에서 함수형 패러다임을 도입한다는 것은 '부수 효과를 없애는 것'이 아니라 '부수 효과를 안전하게 격리하고 통제하는 것'을 의미한다. 순수 함수로 이루어진 '핵심 비즈니스 로직'과, 부수 효과를 일으키는 '외부 세상(UI, DB, Network)'의 경계를 명확히 나누는 것이다.
'우테코 8기' 카테고리의 다른 글
| 레벨 2 회고 (2) | 2026.07.05 |
|---|---|
| 원시값 포장 (0) | 2026.06.21 |
| 우테코 8기 합격 후기(안드로이드) (2) | 2026.04.24 |