[Swift] 함수형 프로그래밍(Functional Programming) 1/2
안녕하세요, iOS 개발하는 루피입니다.
오늘은 함수형 프로그래밍에 대해 정리해보려고 합니다.
자 그럼 바로 시작해보도록 하겠습니다.
함수형 프로그래밍
수학적 함수의 계산을 기반으로 상태 변화와 가변 데이터를 최소화하는 프로그래밍 패러다임이다.
처음부터 어려운 단어들이 나열 되는 군요 .... 수학적 함수,, 상태 변화,, 가변 데이터 너무 어렵습니다 ㅠㅠ
단어의 의미들을 우선 정리해보겠습니다.
수학적 함수란?
수학에서 함수는 입력값을 받아서 항상 동일한 출력값을 반환하는 규칙입니다. 이는 곧 같은 입력값에 대해 항상 같은 결과를 반환한다는 것인데요.
예를 들어..
f(x) = x+2 이면, f(2) = 4, f(3) = 5 가 될것입니다.
이런 경우 f(2) 는 항상 4가 되기 마련입니다. 이는 외부 환경이나 다른 요인에 영향을 받지 않습니다!!
이러한 수학적 함수라는 개념은 프로그래밍에서는 순수 함수라는 개념으로 통합니다.
순수함수는
- 동일한 입력값에 대해 항상 동일한 출력값을 반환합니다.
- 외부 상태(변수, 데이터베이스 등)를 변경하거나 의존하지 않습니다.
코드로 한번더 살펴보겠습니다.
순수 함수
func square(_ x: Int) -> Int {
return x * x
}
print(square(2)) // 항상 4
print(square(3)) // 항상 9
비순수 함수
var multiplier = 2
func multiply(_ x: Int) -> Int {
return x * multiplier // 외부 상태에 의존
}
print(multiply(3)) // multiplier 값에 따라 결과가 달라짐
multiplier = 3
print(multiply(3)) // 결과가 달라짐 (6 → 9)
어떤가요?? 이해가 가시나요? 이해가 가셨다면 오케이 입니다.
이처럼 함수형 프로그래밍에서는 외부 상태에 의존하지 않는 순수 함수를 작성하는 것을 권장합니다!
상태 변화란?
프로그래밍에서 상태는 프로그램이 실행 중에 저장하고 있는 데이터입니다. 상태 변화란 이 데이터를 수정하거나 업데이트 하는 것을 의미합니다.
이게 왜 문제야??? 한번 코드를 통해 살펴보도록 하겠습니다.
var counter = 0
func increment() {
counter += 1 // counter의 상태가 변경됨
}
increment()
print(counter) // 출력: 1
increment()
print(counter) // 출력: 2
지금 위 코드는 간단하지만, 프로그램이 복잡해 질 경우 상태 변화가 어디에서 발생했는지 추적하기가 어려워집니다...
특히 멀티 스레드 환경에서는 여러 스레드가 동시에 counter를 수정하면 문제가 발생할 확률이 더 커지겠죠?
그러면 어떻게 해야 함수형 프로그래밍 방식으로 접근하는 것일까요?
바로 상태변화를 피하고, 데이터를 수정하는 대신 새로운 데이터를 생성하는 것이라고 합니다. 코드로 보시죠
func increment(_ value: Int) -> Int {
return value + 1 // 기존 값을 변경하지 않고 새로운 값을 반환
}
let counter = 0
let newCounter = increment(counter)
print(newCounter) // 출력: 1
let anotherCounter = increment(newCounter)
print(anotherCounter) // 출력: 2
이런식으로 기존의 값을 변경하는 것이 아니라 새로운 데이터를 만들어 사용하는 것이 프로그램의 동작을 예측하기 쉬어지고, 디버깅과 테스트가 간단해집니다!
가변 데이터란?
프로그램 실행 중에 값을 변경할 수 있는 데이터를 의미합니다. Swift에서는 var로 선언된 변수가 가변 데이터일수 있겠네요!!
가변 데이터 사용의 문제점을 코드로 살펴 보시죠
var numbers = [1, 2, 3]
func addNumber(_ array: inout [Int], _ number: Int) {
array.append(number)
}
addNumber(&numbers, 4)
print(numbers) // [1, 2, 3, 4] - 원본 배열이 변경됨!
이렇게 함수의 호출로 원본 데이터를 예상치 못하게 변경 된다면, 디버깅이 어려워 질 수 있습니다!!
그렇다면 불변 데이터를 사용하는 것이 좋겠죠? 코드로 보시죠
let numbers = [1, 2, 3]
func addNumber(_ array: [Int], _ number: Int) -> [Int] {
return array + [number] // 새로운 배열 반환
}
let newNumbers = addNumber(numbers, 4)
print(numbers) // [1, 2, 3] - 원본 배열은 그대로 유지됨!
print(newNumbers) // [1, 2, 3, 4]
이렇게 불변성을 유지하면, 데이터의 변경으로 인한 Side Effect를 방지할 수 있겠죠??
오늘은 함수형 프로그래밍과 그 의미에 대해 알아보았습니다. 다음 글에서는 특징과 명령형 프로그래밍과 차이점을 주로 글을 써보도록 하겠습니다!
오늘도 화이팅입니다!