지난 번에 포스팅 했던 객체 지향 프로그래밍(OOP)에 이어, 이번 포스팅은 함수형 프로그래밍(FP)에 관한 글입니다.
iOS 개발을 위해 Swift를 사용하다보니, OOP뿐만 아니라 FP에 대해 관심이 생겨 공부 해보았습니다.
아마 다음은 프로토콜 지향에 대한 포스팅을 작성할 것 같습니다.
CS에서 항상 나오는 객체 지향이 아닌 새로운 패러다임에 대해 공부하니 자료도 훨씬 적고,
객체 지향과 완전히 다른 방식의 패러다임이라 이해하는 데에 시간이 조금 걸렸네요.
함수형 프로그래밍(Functional Programming, FP)은 자료 처리를 수학적 함수의 계산으로 취급하고 상태와 가변 데이터를 멀리하는 프로그래밍 패러다임.
죽, 데이터를함수로 연결하는 것을 중심
으로 사고하는 프로그래밍 방법.
순수 함수로 나누어 문제를 해결
한다.가독성
을 높이고 유지보수
를 용이하게 한다.함수 자체의 응용
을 중요시 한다.도
가지고 있다.순수 함수
, 1급 객체
, 고차 함수
, 불변성
등이 있다.동일한 입력
에 대해 동일한 출력
을 생성한다.부수 효과(Side effects)가 없다.
쓰레드 안전성을 보장
받을 수 있다.(메모리, I/O의 관점에서 부수 효과가 없기 때문)테스트 및 병렬화가 쉽다.
변수의 값이 변경
된다.제자리에서 수정
한다.콘솔 또는 I/O가 발생
한다.// 순수 함수가 아닌 경우(부수 효과를 가진다)
// result가 var로 선언되었기 때문에 변수의 값이 변경(부수 효과)된다.
var result = 0
func addImpure(a: Int, b: Int) {
result = a + b
}
addImpure(a: 3, b: 4)
print(result) // Output: 7
// 순수 함수의 경우(부수 효과를 가지지 않는다)
func addPure(a: Int, b: Int) -> Int {
return a + b
}
print(addPure(a: 3, b: 4)) // Output: 7
함수를 1급 객체로서 사용
할 수 있다.변수나 데이터 구조
안에 담을 수 있다.파라미터로 전달
할 수 있다.반환값으로 사용
할 수 있다.// 1급 객체로서의 함수(변수 안에 함수가 담겨있다)
let add = { (a: Int, b: Int) -> Int in
return a + b
}
// 함수를 파라미터로 전달
func applyOperation(a: Int, b: Int, operation: (Int, Int) -> Int) -> Int {
return operation(a, b)
}
// 위의 코드 사용
let result = applyOperation(a: 3, b: 4, operation: add)
print(result) // Output: 7
// 1급 객체인 함수를 반환하는 함수
func makeMultiplier(_ multiplier: Int) -> (Int) -> Int {
return { number in
return number * multiplier
}
}
let multiplyByThree = makeMultiplier(3)
print(multiplyByThree(4)) // Output: 12
// 코드 진행 과정
// makeMultiplier 함수는 (Int) -> Int 형태의 함수를 반환하는 함수이다.
// multiplyByThree는 가장 위의 함수 객체(add) 처럼
// number * 3을 반환하는 함수 객체가 된다.
// 따라서, multiplyByThree(4)는 4 * 3을 반환하여 값이 12가 된다.
함수를 1급 객체로서 사용
하는 것을 말한다.추상화와 코드 재사용
을 통해 강력한 메커니즘을 제공하며, 현대 프로그래밍 언어의 주요 특징이다.map, filter, reduce, compactMap
등과 같은 표준 라이브러리에 있는 함수를 통해 데이터에 대한 복잡한 변환을 간결하게 수행
할 수 있다.let numbers = [1, 2, 3, 4, 5]
// map
let squaredNumbers = numbers.map { $0 * $0 }
print(squaredNumbers) // Output: [1, 4, 9, 16, 25]
// filter
let evenNumbers = numbers.filter { $0 % 2 == 0 }
print(evenNumbers) // Output: [2, 4]
// reduce
let sum = numbers.reduce(0) { $0 + $1 }
print(sum) // Output: 15
// forEach
numbers.forEach { print("Number: \($0)") }
// Output:
// Number: 1
// Number: 2
// Number: 3
// Number: 4
// Number: 5
// compactMap
let optionalNumbers: [Int?] = [1, nil, 3, nil, 5]
let unwrappedNumbers = optionalNumbers.compactMap { $0 }
print(unwrappedNumbers) // Output: [1, 3, 5]
// flatMap
let nestedArray = [[1, 2], [3, 4], [5, 6]]
let flattenedArray = nestedArray.flatMap { $0 }
print(flattenedArray) // Output: [1, 2, 3, 4, 5, 6]
불변성을 유지
해야 한다.기존의 데이터는 수정하지 않고
해당 데이터를 통해 새로운 데이터를 만든다.
안전한 코드 작성이 가능
하다.// 불변성의 데이터를 만드는 법은 간단하다.
// Swift에서는 let으로 선언해주면 된다.
let numbers = [1, 2, 3, 4, 5]
let person = ["name": "Jun", "age": 27]
let uniqueNumbers = Set([1, 2, 3, 4, 5])
// 불변성을 가진 변수와 해당 값 수정하는 방법:
// 기존 변수에 새로운 값을 추가한 새로운 변수를 생성
let initialAmount = 100
let finalAmount = initialAmount + 50
print(finalAmount) // Output: 150
// 불변성을 가진 배열과 조건에 맞는 수만을 가진 배열로 수정하는 방법:
// 고차 함수를 이용해 새로운 배열을 생성
let squaredNumbers = numbers.map { $0 * $0 }
let evenNumbers = numbers.filter { $0 % 2 == 0 }
테스트 및 유지보수가 용이
하다.코드 재사용 및 모듈화가 용이
하다.어떻게(How)
보다 무엇을(What)
할 것인지를 강조하기 때문에 가독성
이 좋다.병렬 프로그래밍에 효과적
이다.사고 방식의 전환
이 필요하다.가변성의 데이터를 사용하는 것이 더 효율적인 상황
이 있을 수 있다.디버깅
이 어려울 수 있다.모든 유형의 프로그램
에 적합하지 않을 수 있다.OOP와 FP를 함께 적절하게 사용
하여 보다 나은 소프트웨어를 개발할 수 있다.객체 지향 프로그래밍(OOP) | 함수형 프로그래밍(FP) | |
---|---|---|
개념 | 객체의 개념이 중심 → 객체들끼리 상호작용 | 1급 객체로서 함수의 개념이 중심 → 함수는 값으로 취급되어 변수, 인자, 반환값으로 사용 |
제어 흐름 | 명령형 제어 흐름 구조 | 선언적 제어 흐름 구조 |
부수 효과 | 프로그램에 영향을 미치는 경우가 생길 수 있음 | 순수 함수를 이용하여 최소화를 목표로 함 |
모듈화 | 캡슐화, 상속 및 다형성을 통해 모듈화 | 고차 함수 및 불변 데이터 구조를 통해 모듈화 |