[함수형 프로그래밍] 순수함수 & 익명함수 & 고차함수
2020. 4. 3. 21:00ㆍiOS
Functional Programming?
- 자료 처리를수학적 함수의 계산으로 취급
- 상태와 가변 데이터를 멀리함
- 변수와 반복문이 없음
- Side-effect가 없음 → 동작을 이해하고 예측하기 쉬워짐
- Side-effect → 함수형 프로그래밍에서는 잘못된 code로 인한 오동작의 의미가 아닌 실행 결과 상태의 변화를 일으키는 모든 것을 지칭함
→ 모듈화 수준이 높으면 재사용성이 높고 좋은 프로그래밍이라 할 수 있다.
→ 평가 시점이 무관하다는 특성으로 효율적인 로직을 구성하는 것이 함수형 프로그램의 궁극적인 패러다임
순수함수
- 동일한 인자가 주어졌을 때항상같은 값을 리턴하는 함수
- → side effect 가 없음
- Thread에 안전하고 병렬적인 계산이 가능
- 코드의 블록을 이해하기 위해 일련 상태 갱신을 따라갈 필요가 없고 국소 추론만으로도 코드 이해 가능
- 모듈적인 프로그램은 독립적으로 재사용할 수 있는 구성요소(component)로 구성
// a와 b를 더하는 것 외의 어떤 side-effect도 발생시키지 않음
func addValue(_ a: Int, b: Int) -> Int {
return a + b
}
print(addValue(3, b: 5)) // 8
- 입력과 결과가 분리되어 있으며, 어떻게 사용되는 지에 대해 전혀 신경 쓰지않아도 되므로 재사용성이 높아진다.
순수 함수가 아닌 경우
- 변수가 포함되어 있을 경우
var c = 10
func addValue2(a: Int, b: Int, c: Int) -> Int {
return a + b + c
}
print(addValue2(a: 3, b: 5, c: c)) // 18
c = 20
print(addValue2(a: 3, b: 5, c: c)) // 28
함수 내에서 외부의 c라는'변수'값이 변하면 결과 값도 달라지기 때문
c가 변하지 않는 **'상수'**라면 addValue2는 순수 함수이다.
외부의 값을 참조해도 리턴 값이 동일하다는 것을 보장됨
- 외부 상태에 영향을 미칠 경우(1)
var c = 20
func addValue3(a: Int, b: Int) -> Int {
c = b // 외부 상태에 영향을 미침 -> 부수효과(side-effect)
return a + b
}
print(c) // 20
print(addValue3(a: 3, b: 5)) // 8
print(c) // 5
함수가외부의 값을 변경하는 코드를 가지고 있기 때문 리턴하는 값이 항상 일정하더라도 외부의 상태를 변경하는 코드가 있으면 순수 함수가 아님.
- 외부 상태에 영향을 미칠 경우(2)
class ObjectEx {
var num = 10
}
func addVlaue4(obj: ObjectEx, b: Int) -> Int {
obj.num += b
return obj.num
}
let objEx = ObjectEx()
print(objEx.num) // 10
print(addValue4(obj: objEx, b: 5)) // 15
print(objEx.num) // 15
객체를 인자로 받아서 그 상태를 변경시키는 코드를 가지고 있음
객체를 순수 함수로 나타내기
class ObjectEx {
var num = 10
}
func addValue5(obj: ObjectEx, b: Int) -> Int {
return obj.num + b
}
let objEx = ObjectEx()
print(objEx.num) // 10
print(PureFunction.addValue5(obj: objEx, b: 5)) // 15
print(objEx.num) // 10
ObjectEx의 num의 값만 참조 하는 식으로 작성한다.
순수 함수→ 외부의 상태를 변경하지 않으면서 동일한 인자에 대해 항상 똑같은 값을 리턴하는 함수
익명함수
- 일반 함수의 경우 func + 함수 이름을 선언하고 사용
- swift에서는클로저에 해당
- in키워드를 통해 전달 인자와 실행 코드를 구분
- 함수의 이름을 선언하지 않고 바로 몸체만 만들어 일회용 함수로 사용
func test(_ isFinish: Bool) -> Void {
print("isFinish: \(isFinish)")
}
위의 함수를 익명 함수로 바꾸면
let test = { (isFinish: Bool) -> Void in
print("isFinish: \(isFinish)")
}
{
(매개변수) -> (반환타입) in
실행구문
}
가 된다.
- 컴파일러가 반환 타입을 미리 알고 있다면 반환 타입도생락이 가능하다
let test = { (isFinish: Bool) in
print("isFinish: \(isFinish)")
}
{ (매개변수) in
실행구문
}
경량 문법이 있지만 그것은 클로저에서 공부하시면 될듯
- 클로저 내부 코드가 한 줄이라면return생략 가능
@escaping을 붙이지 않아도 탈출하는 방법
고차함수
- 함수를 매개변수로 받는 함수
- 함수를 반환 값으로 사용하는 함수
- 대표적인 고차 함수로는map,filter,reduce가 있다.
map
- 데이터를 변형하고자 할 때 사용
- 기존 컨테이너의 값은 변경되지 않고 새로운 컨테이너를 생성해 반환
- for-in구문과 별 차이가 없지만 더 좋은 점
- 코드 재사용이 용이
- 컴파일러 최적화 측면에서 성능이 좋음
- 빈 배열 초기 생성할 필요 x
- append연산 수행할 필요 x
- 다중 스레드 환경에서 하나의 컨테이너에 여러 스레드들이 동시에 변경하려고 할 때 예측 못한 결과 발생을 방지할 수 있다
let numbers = [0, 1, 2, 3, 4]
// 이것은 함수형이 아닙니다ㅜ
func multiply2(_ numbers: [Int]) -> [Int] {
var multiplyNumbers = [Int]()
for number in numbers {
multiplyNumbers.append(number * 2)
}
return multiplyNumbers
}
print(multiply2(numbers)) // [0, 2, 4, 6, 8]
// map 을 활용한다면?
print(numbers.map({ (num) -> Int in
num * 2
})) // [0, 2, 4, 6, 8]
print(numbers.map { $0 * 2 }) // [0, 2, 4, 6, 8]
매개 변수로 전달 할 함수를 클로저 상수로 두어 코드를 재사용할 수 있다.
let multiple: (Int) -> Int = { $0 * 2 }
print(numbers.map(multiple) // [0, 2, 4, 6, 8]
filter
- 컨테이너 내부의 값을 걸러서 추출하고자 할 때 사용
- filter를 매개 변수로 전달되는 함수의 반환 타입은Bool
let numbers = [0, 1, 2, 3, 4]
func filterEvens(_ numbers: [Int]) -> [Int] {
var evenNumbers = [Int]()
for number in numbers {
if number % 2 == 0 { evenNumbers.append(number) }
}
return evenNumbers
}
print(filterEvens(numbers)) // [0, 2, 4]
print(numbers.filter({ (num) -> Bool in
num % 2 == 0
})) // [0, 2, 4]
print(numbers.filter({ $0 % 2 == 0 })) // [0, 2, 4]
// 물론 위의 map에서 했듯 클로저 상수로 둘 수 있다.
let filterEven2: (Int) -> Bool = { $0 % 2 == 0}
print(numbers.filter(filterEven2))
map과filter를 연결하여 사용할 수도 있다.
let doubleEven = numbers
.map { $0 + 2 }
.filter { $0 % 2 == 0 }
print(doubleEven) // [2, 4, 6]
reduce
- 컨테이너 내부를 하나로 합쳐주는 기능
- 정수 배열이면 전달받은 함수의 연산 결과로 합쳐주고, 문자열 배열이라면 문자열을 하나로 합쳐준다
- 첫 번째 매개변수를 통해 초깃값을 지정할 수 있다.
- 이 초깃값이 최초의$0으로 사용된다
let numbers = [0, 1, 2, 3, 4]
func reduceNumbers(_ numbers: [Int]) -> Int {
var reduceNumbers = 10 // 초깃값
for number in numbers {
reduceNumbers += number
}
return reduceNumbers
}
print(reduceNumbers(numbers)) // 20
// 여기서도 10이 초깃값
print(numbers.reduce(10, { (result: Int, currentItem: Int) -> Int in
return result + currentItem
})) // 20
print(numbers.reduce(10) { $0 + $1 }) // 20
let texts = ["a", "b", "c", "d"]
print(texts.reduce("") { $0 + $1 }) // abcd
- reduce에서 클로저의 매개변수 이름을 first, second보다는 result, currentItem이라고 하는 것이 좋다.
- result 는 초깃값으로부터 출발하여 마지막 요소까지 순회하는 결과값
- currentItem은 현재 순회하고 있는 요소의 값
- 만약 currentItem들이 없으면 초기값을 반환
reduce(into:_:)
let letters = "abracadabra"
let letterCount = letters.reduce(into: [:]) { counts, letter in
counts[letter, default: 0] += 1
} // ["d": 1, "c": 1, "a": 5, "r": 2, "b": 2]
- 단어 빈도 같은 것에 사용 가능하다
- 결과는 copy-on-write(inout)으로 배열이나 딕셔너리와 같다
forEach
- 순차적으로 element에 접근할 수 있음
- collection에서 제공하는 기능이며 클로저 방식으로 사용됨
- for-in과 다르게 중간에 break로 탈출하지 못함
- return을 하여 종료하지만 element를 인자로 가진 클로저가 실행이 된다.
enum Calculator {
case nomal
case multiple
case plus
var calc: (Int) -> Void {
switch self {
case .nomal: return {print($0)}
case .multiple: return {print($0 * $0)}
case .plus: return {print($0 + $0)}
}
}
}
let calc: Calculator = Calculator.nomal
let numbers: [Int] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
numbers.forEach(calc.calc) //enum 내부의 함수를 실행
flatMap
- flatten + map의 합성
- map에서 Optional로 둘러싸진 결과에서 Optional을 풀어낸 값이 나올 수 있게 한 것
- swift4 이후 compactMap으로 변경
// 2차 배열을 1차 배열로 합성
let arr = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
let flatArr = arr.flatMap { $0 }
print(flatArr) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
// Optional 제거
let a = [1, 2, 3, 4, 5]
let c: (Int) -> Int? = { n in
if n % 2 == 0 {
return n * 2
}
return nil
}
print(a.map(c)) // [nil, Optional(4), nil, Optional(8), nil]
let b = a.compactMap(c)
print(b) // [4, 8]
참고
순수 함수란? (함수형 프로그래밍의 뿌리, 함수의 부수효과를 없앤다)
[Swift] 익명 함수(Anonymous Functions)
'iOS' 카테고리의 다른 글
[함수형 프로그래밍] Maybe & Either (0) | 2020.04.10 |
---|---|
[함수형 프로그래밍] Functor와 Monad (0) | 2020.04.09 |
[함수형 프로그래밍] 일급 함수 (0) | 2020.04.04 |