일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- DispatchGroup
- iOS배포
- 스위프트
- Xib
- 개발기록
- jQuery
- AJAX
- iOS계산기
- Xcode
- 파이썬서버
- JavaScript
- Swift
- iOS앱배포
- FileOwner
- spring
- subscript
- 스프링
- Python
- FLASK
- ios
- 딩동말씀
- 앱배포
- 자바스크립트
- 계산기앱
- 앱버전구하기
- 맥
- 웹
- 계산기앱만들기
- MainScheduler
- customclass
- Today
- Total
개발하는 뚝딱이
[swift] 모나드 본문
책 <스위프트 프로그래밍 3판 ;야곰 지음> 을 정리한 글입니다.
모나드
- 함수형 프로그래밍에서 모나드는 순서가 있는 연산을 처리할 때 자주 활용하는 디자인 패턴 (수학의 모나드와 다른 의미)
- 프로그래밍에서 모나드가 갖춰야 할 조건
- 타입을 인자로 받는 타입 (특정 타입의 값을 포장)
- 특정 타입의 값을 포장한 것을 반환하는 함수(메서드)가 존재
- 포장된 값을 변환하여 같은 형태로 포장하는 함수(메서드)가 존재
1. 컨텍스트
- Context ; 콘텐츠를 담을 수 있는 컨테이너 역할 (물컵에 물이 담겨져 있을 때 ; 물컵 - 컨텍스트, 물 - 콘텐츠)
- 옵셔널은 열거형으로 구현되어 있어 case의 연관 값을 통해 인스턴스 안에 연관 값을 갖는 형태
- 옵셔널에 값이 없으면 열거형의 .none case로, 값이 있으면 열거형의 .some(value) case로 값을 갖게 된다
→ 옵셔널의 값을 추출하는 것은 열거형 인스턴스 내부의 .some(value) case의 연관 값을 꺼내오는 것 과 같음
- Optional은 Wrapped 타입을 인자로 받는 (제네릭) 타입 → 모나드의 첫 번째 조건 만족
- Optional 타입은 Optional<Int>.init(2)처럼 다른 타입(Int)의 값을 갖는 상태의 컨텍스트를 생성할 수 있음
→ 모나드의 두 번째 조건 만족
func addThree(_ num: Int) -> Int {
return num + 3
}
- addThree(_:) 의 함수의 전달인자로 컨텍스트에 들어있지 않은 순수 값인 2를 전달하면 정상적으로 함수 실행 가능
- 그러나 addThree(Optional(2)) 실행 시 오류 발생, 옵셔널이란 컨텍스트로 둘러싸여 전달되었기 때문
2. 함수 객체
- 맵은 컨테이너의 값을 변형시킬 수 있는 고차함수
- 옵셔널은 컨테이너와 값을 갖기 때문에 맵 함수를 사용할 수 있음
Optional(2).map(addThree) // 맵 메서드를 사용하여 옵셔널 연산 가능
// 옵셔널에 메서드와 클로저의 사용
var value: Int? = 2
value.map({ $0 + 3 }) // Optional(5)
value = nil
value.map({ $0 + 3 }) // nil
// Optional의 map 메서드 구현
extension Optional {
func map<U>(f: (Wrapped) -> U) -> U? {
switch self {
case .some(let x): return f(x)
case .none: return .none
}
}
}
- 옵셔널의 map(_:) 메서드를 호출하면 옵셔널 스스로 값이 있는지 없는지 switch 구문으로 판단
- 값이 있으면 전달받은 함수에 자신의 값을 적용한 결괏값을 다시 컨텍스트에 넣어 반환, 값이 없으면 함수를 실행하지 않고 빈 컨텍스트 반환
3. 모나드
- 함수객체 중 자신의 컨텍스트와 같은 컨텍스트의 형태로 맵핑할 수 있는 함수객체를 '닫힌 함수객체' Endofunctor 라고 함
- 모나드는 닫힌 함수객체
- 함수객체는 포장된 값에 함수를 적용할 수 있음
- 모나드도 컨텍스트에 포장된 값을 처리하여 포장된 값을 컨텍스트에 다시 반환하는 함수(맵)을 적용 할 수 있음
- 매핑의 결과가 함수 객체와 같은 컨텍스를 반환하는 함수객체를 모나드라고 할 수 있으며, 이런 매핑을 수행하도록 플랫맵 (flatMap) 이라는 메서드를 활용
- 플랫맵은 맵과 같이 함수를 매개변수로 받고, 옵셔널은 모나드이므로 플랫맵을 사용할 수 있음
// doubleEven(_:) 함수와 플랫맵의 사용
func doubleEven(_ num: Int) -> Int? {
if num.isMultiple(of: 2) {
return num * 2
}
return nil
}
Optional(3).flatMap(doubleEven) // nil
Optional.none.flatMap(doubleEven) // nil
- 플랫맵은 맵과 다르게 컨텍스트 내부의 컨텍스트를 모두 같은 위상으로 평평하게 펼쳐준다는 차이가 있음
- 내부의 값을 1차원적으로 펼쳐놓는 작업을 함
- Optional 타입에 사용하였던 flatMap(_:) 메서드를 Sequence 타입이 Optional 타입의 Element를 포장한 경우 compactMap(_:)이라는 이름으로 사용
- 이 경우를 제외한 다른 경우 그대로 flatMap(_:)이라는 이름을 사용
- compactMap(_:)과 flatMap(_:)의 사용 방법은 같으나 좀 더 분명한 뜻을 나타내기 위해 구분
// 맵과 컴펙트맵(플랫맵)의 차이
let optionals: [Int?] = [1, 2, nil, 5]
let mapped: [Int?] = optionals.map { $0 }
let compactMapped: [Int] = optionals.compactMap { $0 }
print(mapped) // [Optional(1), Optional(2), nil, Optional(5)]
print(compactMapped) // [1, 2, 5]
// 중첩된 컨테이너에서 맵과 플랫맵(콤팩트맵)의 차이
let multipleContainer = [[1, 2, Optional.none], [3, Optional.none], [4, 5, Optional.none]]
let mappedMultipleContainer = multipleContainer.map {$0.map{$0}}
let flatMappedMultipleContainer = multipleContainer.flatMap {$0.compactMap{$0}}
print(mappedMultipleContainer) // [[Optional(1), Optional(2), nil], [Optional(3), nil], [Optional(4), Optional(5), nil]]
print(flatMappedMultipleContainer) // [1, 2, 3, 4, 5]
func stringToInteger(_ string: String) -> Int? {
return Int(string)
}
func integerToString(_ integer: Int) -> String? {
return "\(integer)"
}
var optionalString: String? = "2"
let flattenResult = optionalString.flatMap(stringToInteger)
.flatMap(integerToString)
.flatMap(stringToInteger)
print(flattenResult) // Optional(2)
let mappedReusult = optionalString.map(stringToInteger) // 더 이상 체인 연결불가
print(mappedReusult) // Optional(Optional(2))
- 플랫맵은 함수의 결괏값에 값이 있다면 추출해서 평평하게 만드는 과정을 내포함
- 따라서 항상 같은 컨텍스트를 유지할 수 있으므로 연쇄 연산도 가능
// 옵셔널의 맵과 플랫맵의 정의
func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?
func flatMap<U>(_ transform: (Wrapped) throws -> U?) rethrows -> U?
- 맵에서 전달받은 trnasform은 포장된 값을 매개변수로 갖고 U를 반환하는 함수
- stringToInt(_:)는 String 타입을 전달받고 Int? 타입을 반환, U == Int?가 됨
- String 옵셔널의 맵에 stringToInt(_:) 함수를 전달하면 최종 반환 타입이 Int??가 됨
- 플랫맵이 전달받은 transform은 포장된 값을 매개변수로 갖고 U?를 반환하는 함수
- stringToInt(_:)를 대입하면 U? == Int?가 됨
- U == Int가 되므로 최종적으로 Int? 타입 반환
// 옵셔널 바인딩을 통한 연산
var result: Int?
if let string: String = optionalString,
let number: Int = stringToInteger(string),
let finalString: String = integerToString(number),
let finalNumber: Int = stringToInteger(finalString) {
result = Optional(finalNumber)
}
print(result) // Optional(2)
// 플랫맵 체이닝 중 빈 컨텍스트를 만났을 때의 결과
func integerToNil(param: Int) -> String? {
return nil
}
optionalString = "2"
result = optionalString.flatMap(stringToInteger)
.flatMap(integerToNil)
.flatMap(stringToInteger)
print(result) // nil
- 플랫맵에서 Optional.none 즉, nil을 반환하면 그 이후에 호출되는 메서드는 무시
- 옵셔널이 모나드이기 때문에 가능
'swift' 카테고리의 다른 글
[swift] 상속 (0) | 2020.09.05 |
---|---|
[swift] 서브스크립트 (0) | 2020.06.25 |
[swift] 타입캐스팅 (0) | 2020.05.17 |
[swift] 맵, 필터, 리듀스 (0) | 2020.05.13 |
[swift] 옵셔널 체이닝과 빠른 종료 (0) | 2020.05.08 |