티스토리 뷰
개발을 하다 보면 한 번쯤 이런 고민을 하게 된다.
"이 코드... 왜 이렇게 복잡하지?"
"여기저기 상태를 바꾸다 보니 어디서 문제가 터지는지 모르겠네;;"
"어떻게 하면 더 깔끔하게, 유지보수하기 좋은 코드를 짤 수 있을까?"
그럴 때 등장하는 개념이 바로 함수형 프로그래밍(Functional Programming, FP)이다.
솔직히 말하면, 처음에는 이게 뭔지도 모르고 그냥 고차함수만 쓰면서 편리하네.. 생각하고 딱히 고민해본적이 없었다
"그냥 함수를 잘 쓰는 거 아냐?" 싶었는데, 이것을 공부 하고나서 쓰면 쓸수록 "아... 이래서 FP가 좋은 거구나" 하고 깨닫게 되었다.
오늘은 내가 FP를 배우면서 했던 고민들과, 실제 Swift에서 어떻게 적용할 수 있는지를 이야기해보려고 한다.
함수형 프로그래밍(FP)이란?
FP는 한마디로 "데이터를 변경하지 않고, 순수 함수(Pure Function)만을 사용해서 프로그래밍하는 방식"이다.
이게 뭔 말인지 감이 잘 안 올 수 있는데, 예제를 보면서 차근차근 이해해 보자.
FP vs 일반적인(OOP) 방식 비교
1) OOP 방식 (명령형 프로그래밍)
우선, 우리가 흔히 쓰는 객체지향(OOP) 스타일의 코드를 보자.
class Counter {
var value = 0
func increment() {
value += 1
}
}
let counter = Counter()
counter.increment()
print(counter.value) // 1
여기서 counter.increment()를 호출하면 value가 1 증가한다.
이게 문제가 될까?
2) FP 방식 (선언형 프로그래밍)
함수형 프로그래밍에서는 이렇게 하지 않는다.
상태를 변경하지 않고, 새로운 값을 반환한다.
func increment(_ value: Int) -> Int {
return value + 1
}
let count = 0
let newCount = increment(count)
print(newCount) // 1
??? "뭐가 다른데?"
- OOP 방식에서는 value라는 상태(state) 를 직접 변경한다.
- FP 방식에서는 기존 값(count)을 변경하지 않고 새로운 값을 반환한다.
이 차이가 왜 중요할까?
→ 상태를 변경하지 않으면 버그 발생 가능성이 확 줄어든다.
FP의 핵심 개념
FP가 왜 좋은지 이해하려면, 몇 가지 중요한 개념을 알아야 한다.
1) 순수 함수(Pure Function)
- 같은 입력을 넣으면 항상 같은 출력을 반환하는 함수
- 외부 상태를 변경하지 않는다.
func add(a: Int, b: Int) -> Int {
return a + b
}
이 함수는 완벽한 순수 함수다.
입력값 a, b가 같으면 항상 같은 결과를 반환한다.
하지만 이런 함수는?
var total = 0
func addToTotal(_ value: Int) {
total += value
}
addToTotal(5)
print(total) // 5
이건 순수 함수가 아니다!
왜냐하면 total이라는 외부 상태를 변경하기 때문이다.
이렇게 하면 예상치 못한 버그가 발생할 가능성이 높아진다.
2) 불변성(Immutability)
FP에서는 변수를 변경하지 않는다.
대신, 새로운 값을 반환하는 방식을 선호한다.
struct User {
let name: String
let age: Int
}
func updateAge(user: User, newAge: Int) -> User {
return User(name: user.name, age: newAge)
}
let user1 = User(name: "Alice", age: 20)
let user2 = updateAge(user: user1, newAge: 21)
print(user1.age) // 20 (변경되지 않음)
print(user2.age) // 21 (새로운 객체 생성)
이렇게 하면 원본 데이터가 변경되지 않으면서도, 새로운 값을 쉽게 만들 수 있다.
"변경 가능한 상태를 최소화하라!"
이게 FP의 핵심 철학이다.
3) 고차 함수(Higher-Order Function)
FP에서는 함수를 매개변수로 전달하거나, 반환할 수 있다.
이걸 활용하면 더 유연하고 재사용 가능한 코드를 만들 수 있다.
func applyOperation(_ a: Int, _ b: Int, operation: (Int, Int) -> Int) -> Int {
return operation(a, b)
}
let sum = applyOperation(3, 4, operation: { $0 + $1 })
let product = applyOperation(3, 4, operation: { $0 * $1 })
print(sum) // 7
print(product) // 12
이걸 보면, applyOperation 함수는 더하기든 곱하기든 어떤 연산이든 적용할 수 있도록 유연하게 설계됐다.
이게 바로 FP의 장점이다!
4) 맵(map), 필터(filter), 리듀스(reduce)
Swift에서는 FP 개념을 쉽게 활용할 수 있도록, 배열에서 자주 쓰이는 고차 함수를 제공한다.
let numbers = [1, 2, 3, 4, 5]
// map: 각 요소에 2를 곱함
let doubled = numbers.map { $0 * 2 }
print(doubled) // [2, 4, 6, 8, 10]
// filter: 짝수만 남김
let evens = numbers.filter { $0 % 2 == 0 }
print(evens) // [2, 4]
// reduce: 모든 숫자를 더함
let sum = numbers.reduce(0, +)
print(sum) // 15
이 함수들이 왜 좋냐면??
- 기존 배열을 변경하지 않고, 새로운 배열을 반환한다.
- for 루프 없이도 간결하게 데이터 변환이 가능하다.
FP는 언제 유용할까?
✅ 멀티스레드 환경에서 안정적인 코드가 필요할 때
- FP에서는 상태 변경을 하지 않기 때문에, 동시성 문제를 줄일 수 있다.
✅ 데이터 변환을 쉽게 하고 싶을 때
- map, filter, reduce를 활용하면 코드가 훨씬 깔끔해진다.
✅ 재사용 가능한 코드를 만들고 싶을 때
- 고차 함수를 활용하면 같은 로직을 여러 곳에서 쉽게 재사용할 수 있다.
정리 – FP는 언제나 정답일까?
솔직히 말해서, FP가 만능은 아니다.
- FP를 너무 고집하면 코드가 오히려 이해하기 어려워질 수도 있다.
- Swift는 완전한 FP 언어가 아니라, OOP와 FP를 적절히 섞어서 사용해야 한다.
하지만!
불필요한 상태 변경을 줄이고 싶다면??? FP를 활용해보자.
더 간결하고 가독성 좋은 코드를 만들고 싶다면?? FP 스타일을 고민해보자.
쓰면 쓸수록 FP 스타일이 주는 안정성과 가독성의 힘을 느낄수 있을것이다!
'프로그래밍 공부' 카테고리의 다른 글
| ARC(Automatic Reference Counting) – Swift는 어떻게 메모리를 관리할까? (1) | 2025.02.20 |
|---|---|
| Swift – iOS 개발자가 알아야 할 모든 것 (2) | 2025.02.19 |
| 객체 지향 설계의 SOLID 원칙 – 제대로 알고 써보자! (0) | 2025.02.18 |
| 반응형 프로그래밍(React Programming, RP) – 데이터를 흐름처럼 다루는 방식 (0) | 2025.02.15 |
| 프로토콜 지향 프로그래밍 (POP, Protocol-Oriented Programming) (0) | 2025.02.13 |